dostępne na: github
require("dplyr")
require("ggplot2")
require("GGally")
require("MASS")
require("cowplot")
require("car")
require("liver")
Poniższe dane pochodzą z serwisu Kaggle i przedstawiają dane dla małej próbki amerykańskiej populacji w zakresie kosztów ubezpieczenia zdrowotnego na podstawie kilku atrybutów opisanych w sekcji Zawartość.
zmienna | opis |
---|---|
age | wiek opłacającego ubezpieczenie |
sex | płeć opłacającego ubezpieczenie |
bmi | indeks masy ciała (Body Mass Index, BMI) ubezpieczonego |
children | liczba dzieci objętych tym samym ubezpieczeniem/liczba zależnych beneficjentów |
smoker | czy ubezpieczony jest palaczem? |
region | okręg zamieszkania ubezpieczonego w USA: northeast, southeast, southwest, northwest |
charges | koszty ubezpieczenia |
źródło: github
Celem było stworzenie najlepszego modelu, który na podstawie podanych zmiennych umożliwiłby wymodelowanie kosztu ubezpieczenia. Następnie należało sprawdzić, które zmienne mają największy wpływ na model i uprościć go, koncentrując się głównie na tych zmiennych.
df <- read.csv("data/mcp.txt", stringsAsFactors = T)
head(df)
## age sex bmi children smoker region charges
## 1 19 female 27.900 0 yes southwest 16884.924
## 2 18 male 33.770 1 no southeast 1725.552
## 3 28 male 33.000 3 no southeast 4449.462
## 4 33 male 22.705 0 no northwest 21984.471
## 5 32 male 28.880 0 no northwest 3866.855
## 6 31 female 25.740 0 no southeast 3756.622
n <- length(df$charges)
df %>%
ggpairs(aes(color = sex, alpha = 0.7),
upper = list(continuous = wrap("cor", size = 2.5))
) +
theme(
axis.ticks.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank()
)
str(df)
## 'data.frame': 1338 obs. of 7 variables:
## $ age : int 19 18 28 33 32 31 46 37 37 60 ...
## $ sex : Factor w/ 2 levels "female","male": 1 2 2 2 2 1 1 1 2 1 ...
## $ bmi : num 27.9 33.8 33 22.7 28.9 ...
## $ children: int 0 1 3 0 0 0 1 3 2 0 ...
## $ smoker : Factor w/ 2 levels "no","yes": 2 1 1 1 1 1 1 1 1 1 ...
## $ region : Factor w/ 4 levels "northeast","northwest",..: 4 3 3 2 2 3 3 2 1 2 ...
## $ charges : num 16885 1726 4449 21984 3867 ...
summary(df)
## age sex bmi children smoker
## Min. :18.00 female:662 Min. :15.96 Min. :0.000 no :1064
## 1st Qu.:27.00 male :676 1st Qu.:26.30 1st Qu.:0.000 yes: 274
## Median :39.00 Median :30.40 Median :1.000
## Mean :39.21 Mean :30.66 Mean :1.095
## 3rd Qu.:51.00 3rd Qu.:34.69 3rd Qu.:2.000
## Max. :64.00 Max. :53.13 Max. :5.000
## region charges
## northeast:324 Min. : 1122
## northwest:325 1st Qu.: 4740
## southeast:364 Median : 9382
## southwest:325 Mean :13270
## 3rd Qu.:16640
## Max. :63770
Wśród danych znajdują się zmienne kategorialne: sex, smoker i region,
które posiadają odpowiednio 2, 2 i 4 kategorie. Pozostałe zmienne są to
zmienne numeryczne. Powyżej przedstawione są wykresy, które pokazują
zależności między każdą zmienną a pozostałymi zmiennymi. W zależności od
typu danych wykorzystuje się różne rodzaje wykresów. Jeśli mamy do
czynienia z dwoma zmiennymi numerycznymi, stosuje się wykres korelacji,
natomiast w przypadku zmiennej numerycznej kontra kategorialna korzysta
się z wykresu pudełkowego. Histogramy pokazują rozkład danej cechy.
Podstawowe statystyki dla danych kategorialnych pokazują, że w przypadku
zmiennej smoker
dane są niezbalansowane, ale są
zrównoważone w przypadku pozostałych zmiennych kategorialnych. Zmienne
numeryczne nie wykazują znaczących odchyleń w statystykach podstawowych.
Średnia i mediana są na podobnym poziomie, a kwantyle są równomiernie
rozmieszczone. Jedynie zmienna charges wykazuje skośność i przesunięcie
w lewo, co oznacza, że ma tendencję do przyjmowania niższych wartości,
ale jednocześnie posiada ciężki ogon dla wartości wysokich.
hist.age <- ggplot(data = df, aes(age)) +
geom_histogram()
hist.bmi <- ggplot(data = df, aes(bmi)) +
geom_histogram()
hist.children <- ggplot(data = df, aes(children)) +
geom_histogram(bins = 5)
hist.charges <- ggplot(data = df, aes(charges)) +
geom_histogram()
plot_grid(hist.age, hist.bmi, hist.children, hist.charges, labels = "auto", align = "hv")
box.age <- ggplot(data = df, aes(age)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.5) +
coord_flip() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
box.bmi <- ggplot(data = df, aes(bmi)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.5) +
coord_flip() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
box.children <- ggplot(data = df, aes(children)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.5) +
coord_flip() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
box.charges <- ggplot(data = df, aes(charges)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.5) +
coord_flip() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
plot_grid(box.age, box.bmi, box.children, box.charges, labels = "auto", align = "hv")
Zarówno histogramy, jak i wykresy pudełkowe pozwalają nam zobaczyć
rozkład danych numerycznych. Na podstawie tych wykresów można
wnioskować, że zmienne children
i charges
prawdopodobnie mają rozkład Poissona, z kolei zmienna age wykazuje cechy
rozkładu jednostajnego. Natomiast zmienna BMI prawdopodobnie ma rozkład
normalny, choć na wykresie pudełkowym można zauważyć wydłużony ogon w
kierunku wyższych wartości, co może wpływać na percepcję danych.
age.v.bmi <- ggplot(data = df, aes(age, bmi, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
geom_smooth(color = "red", fill = "blue") +
ggtitle(sprintf("r = %.4f", cor(df$age, df$bmi))) +
theme(legend.position = "none")
age.v.children <- ggplot(data = df, aes(age, children, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
ggtitle(sprintf("r = %.4f", cor(df$age, df$children))) +
theme(legend.position = "none")
age.v.charges <- ggplot(data = df, aes(age, charges, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
ggtitle(sprintf("r = %.4f", cor(df$age, df$charges))) +
theme(legend.position = "none")
children.v.bmi <- ggplot(data = df, aes(children, bmi, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
ggtitle(sprintf("r = %.4f", cor(df$children, df$bmi))) +
theme(legend.position = "none")
children.v.charges <- ggplot(data = df, aes(children, charges, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
ggtitle(sprintf("r = %.4f", cor(df$children, df$charges))) +
theme(legend.position = "none")
charges.v.bmi <- ggplot(data = df, aes(charges, bmi, color = sex)) +
geom_point(size = 2.5, alpha = 0.7) +
ggtitle(sprintf("r = %.4f", cor(df$charges, df$bmi)))
plot_grid(age.v.bmi, age.v.children, age.v.charges, children.v.bmi,
align = "hv",
children.v.charges, charges.v.bmi, labels = "auto", ncol = 2
)
Zbadano korelację między danymi numerycznymi w celu sprawdzenia, czy
istnieje jakakolwiek zależność między nimi. Jak można zauważyć na
powyższych wykresach, żadne z danych numerycznych nie wykazuje istotnej
korelacji. Jednak na niektórych wykresach można dostrzec wyodrębnione
grupy. Na przykład na wykresie charges
w zależności od age
można zaobserwować trzy linie, co sugeruje istnienie trzech populacji
osób, które, niezależnie od wieku, płacą określone poziomy składek.
Obserwuje się również pewną korelację, gdzie wraz z wiekiem wzrasta
wysokość składek, ale słaby współczynnik korelacji może wynikać z
obecności dwóch populacji powyżej pierwszej. Również na wykresie
bmi
w zależności od charges
można zauważyć
wyodrębnienie dwóch populacji, gdzie dla wyższych składek populacja ma
wyższe wartości bmi
. To sugeruje, że zachowanie się danych
różni się dla wyższych i niższych składek, co może utrudniać określenie
jednego trendu dla wszystkich danych.
# liniowy zgeneralizowany
df.lm1 <- lm(charges ~ ., data = df)
summary(df.lm1)
##
## Call:
## lm(formula = charges ~ ., data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -11304.9 -2848.1 -982.1 1393.9 29992.8
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -11938.5 987.8 -12.086 < 2e-16 ***
## age 256.9 11.9 21.587 < 2e-16 ***
## sexmale -131.3 332.9 -0.394 0.693348
## bmi 339.2 28.6 11.860 < 2e-16 ***
## children 475.5 137.8 3.451 0.000577 ***
## smokeryes 23848.5 413.1 57.723 < 2e-16 ***
## regionnorthwest -353.0 476.3 -0.741 0.458769
## regionsoutheast -1035.0 478.7 -2.162 0.030782 *
## regionsouthwest -960.0 477.9 -2.009 0.044765 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6062 on 1329 degrees of freedom
## Multiple R-squared: 0.7509, Adjusted R-squared: 0.7494
## F-statistic: 500.8 on 8 and 1329 DF, p-value: < 2.2e-16
# wykresy diagnostyczne
par(mfrow = c(2, 2))
plot(df.lm1, pch = 16)
Następnie przystąpiono do utworzenia modelu liniowego, uwzględniającego wszystkie dostępne dane. Wartość współczynnika determinacji \(R2\) nie jest wystarczająca, ale nie jest również najgorsza.
Wykres Residuals vs Fitted, przedstawiający na osi \(X\) wartości dopasowane, a na osi \(Y\) różnice między wartościami dopasowanymi
a przewidywanymi, ukazuje istnienie trzech populacji. Prawdopodobnie
istnieje czynnik nieuwzględniony w danych, który odpowiada za ten stan
rzeczy. Dopasowanie danych dla niższych wartości charges
jest znacznie lepsze niż dla wyższych, gdzie obserwuje się
przeszacowanie lub niedoszacowanie.
Wykres Normal Q-Q wyraźnie odbiega od rozkładu normalnego, ponieważ dane w wąskim zakresie osadzone są wzdłuż linii. Krzywa odchylona ku górze po prawej stronie wykresu wskazuje na wysoką liczbę dużych wartości skrajnych lub prawoskośność.
Wykres skali i lokalizacji ponownie ujawnia obecność dwóch populacji.
Dla niższych wartości charges
dane są rozproszone wokół
linii, ale można również zauważyć wiele punktów odstających. Natomiast
druga populacja znajduje się wyżej na wykresie, co wskazuje na
nieodpowiednie dopasowanie modelu dla wyższych wartości, gdzie
przewidywane wartości znacznie różnią się od rzeczywistych.
Ostatni z czterech wykresów ponownie ukazuje trzy populacje, choć nie jest to oczywiste i występuje wiele wartości odstających.
# obserwacje wplywowe i odstajace
par(mfrow = c(1, 2))
cutoff <- 4 / (n - length(df.lm1$coefficients) - 2)
influencePlot(df.lm1, fill.alpha = 1, fill.col = "red")
## StudRes Hat CookD
## 439 -1.001958 0.015980803 0.001811548
## 544 3.827591 0.012173584 0.019856755
## 578 4.219800 0.009354004 0.018448566
## 1086 -1.754152 0.018123011 0.006300674
## 1301 5.009599 0.006920097 0.019084806
plot(df.lm1, which = 4, cook.levels = cutoff, lwd = 2)
abline(h = cutoff, col = "red", lty = 2, lwd = 2)
W celu wykrycia obserwacji odstających i wpływowych zastosowano następujące parametry:
Studentized residuals: służy do oceny, czy dana obserwacja jest wartością odstającą pod względem reszt. Im większa wartość bezwzględna studentized residuals, tym bardziej odstająca jest dana obserwacja.
Hat-values: określają wpływ poszczególnych obserwacji na dopasowanie modelu. Przyjmują wartości między 0 a 1, gdzie wartości bliższe 1 oznaczają większy wpływ.
Cook’s D: miara wpływu obserwacji na dopasowanie modelu regresji oraz na wartości estymowanych parametrów. Wartość progu krytycznego dla Cook’s D, oznaczanego jako cutoff, jest ustalana na podstawie wzoru \(\mathrm{cutoff} = \frac{4}{(n - p - 2)}\), gdzie \(n\) to liczba obserwacji w danych, a \(p\) to liczba parametrów (współczynników) w modelu regresji, wliczając w to wyraz wolny.
Analiza wskazuje na istnienie wielu obserwacji, które potencjalnie mogą być uznane za odstające, co jest zgodne z wcześniejszymi wykresami.
df.lm1.AIC <- AIC(df.lm1)
df.lm1.AIC
## [1] 27115.51
\(AIC\) uwzględnia zarówno dopasowanie modelu do danych, jak i złożoność modelu, dążąc do znalezienia równowagi między tymi dwoma czynnikami. Jego celem jest wybór modelu, który dobrze uogólnia dane, minimalizując jednocześnie złożoność modelu. Dlatego właśnie wybraliśmy \(AIC\) jako miarę oceny naszego modelu. Im mniejsza wartość \(AIC\), tym lepsze dopasowanie modelu, stąd widać, że powyższy model nie jest najlepszym dopasowaniem.
Następnie skonstruowano nowy model, uwzględniając liczbę predyktorów równą liczbie współczynników (coefficients) o najniższych wartościach \(p\) (ozn. ***).
df.lm2 <- lm(charges ~ age + bmi + children + smoker, data = df)
summary(df.lm2)
##
## Call:
## lm(formula = charges ~ age + bmi + children + smoker, data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -11897.9 -2920.8 -986.6 1392.2 29509.6
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -12102.77 941.98 -12.848 < 2e-16 ***
## age 257.85 11.90 21.675 < 2e-16 ***
## bmi 321.85 27.38 11.756 < 2e-16 ***
## children 473.50 137.79 3.436 0.000608 ***
## smokeryes 23811.40 411.22 57.904 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6068 on 1333 degrees of freedom
## Multiple R-squared: 0.7497, Adjusted R-squared: 0.7489
## F-statistic: 998.1 on 4 and 1333 DF, p-value: < 2.2e-16
par(mfrow = c(2, 2))
plot(df.lm2, pch = 16)
par(mfrow = c(1, 2))
cutoff <- 4 / (n - length(df.lm2$coefficients) - 2)
influencePlot(df.lm2, fill.alpha = 1, fill.col = "red")
## StudRes Hat CookD
## 544 3.809648 0.010485091 0.030448715
## 578 4.347011 0.005089296 0.019076254
## 1048 1.615743 0.015136582 0.008014970
## 1086 -1.827707 0.014660714 0.009923179
## 1301 4.916443 0.004483168 0.021398531
plot(df.lm2, which = 4, cook.levels = cutoff, lwd = 2)
abline(h = cutoff, col = "red", lty = 2, lwd = 2)
# czy jest roznica miedzy tymi modelami?
df.lm2.AIC <- AIC(df.lm2)
df.lm2.AIC
## [1] 27114.04
anova(df.lm1, df.lm2)
## Analysis of Variance Table
##
## Model 1: charges ~ age + sex + bmi + children + smoker + region
## Model 2: charges ~ age + bmi + children + smoker
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 1329 4.8840e+10
## 2 1333 4.9078e+10 -4 -238917273 1.6253 0.1654
Korelacje, wykresy, wyniki oraz ocena modelu praktycznie nie uległy
zmianie. Ponieważ nie było istotnej różnicy między modelami, pozostano
przy modelu prostszym (df.lm2
).
Zdecydowano się więc przeprowadzić transformację zmiennych. W tym celu najpierw sprawdzono, czy dane numeryczne, które uwzględniliśmy w modelu (z wyjątkiem zmiennej smoker), mają rozkład normalny.
shapiro.test(df$age)
##
## Shapiro-Wilk normality test
##
## data: df$age
## W = 0.9447, p-value < 2.2e-16
shapiro.test(df$bmi)
##
## Shapiro-Wilk normality test
##
## data: df$bmi
## W = 0.99389, p-value = 2.605e-05
shapiro.test(df$children)
##
## Shapiro-Wilk normality test
##
## data: df$children
## W = 0.82318, p-value < 2.2e-16
Okazało się, że żadna z tych zmiennych nie spełniała tego warunku, co potwierdziła również wcześniejsza analiza histogramów oraz wykresów pudełkowych.
Przeprowadzono transformację Boxa-Coxa w celu znalezienia optymalnej wartości \(\lambda\), która pozwoliłaby przekształcić dane tak, aby przybliżyć je do rozkładu normalnego.
age.fit <- boxCox(lm(age ~ 1, data = df))
age.lambda <- age.fit$x[which.max(age.fit$y)]
age.lambda
## [1] 0.6262626
df$age.boxcox <- (df$age^age.lambda - 1) / age.lambda
bmi.fit <- boxCox(lm(bmi ~ 1, data = df))
bmi.lambda <- bmi.fit$x[which.max(bmi.fit$y)]
bmi.lambda
## [1] 0.4646465
df$bmi.boxcox <- (df$bmi^bmi.lambda - 1) / bmi.lambda
children.fit <- boxCox(lm(children + 1 ~ 1, data = df))
children.lambda <- children.fit$x[which.max(children.fit$y)]
children.lambda
## [1] -0.3838384
df$children.boxcox <- ((df$children + 1)^children.lambda - 1) / children.lambda
Następnie sprawdzono, czy dopasowane \(\lambda\) wpłynęło na zmianę rozkładu danych.
shapiro.test(df$age.boxcox)
##
## Shapiro-Wilk normality test
##
## data: df$age.boxcox
## W = 0.9439, p-value < 2.2e-16
shapiro.test(df$bmi.boxcox)
##
## Shapiro-Wilk normality test
##
## data: df$bmi.boxcox
## W = 0.99858, p-value = 0.3492
shapiro.test(df$children.boxcox)
##
## Shapiro-Wilk normality test
##
## data: df$children.boxcox
## W = 0.81148, p-value < 2.2e-16
hist.age.boxcox <- ggplot(data = df, aes(age.boxcox)) +
geom_histogram()
hist.bmi.boxcox <- ggplot(data = df, aes(bmi.boxcox)) +
geom_histogram()
hist.children.boxcox <- ggplot(data = df, aes(children.boxcox)) +
geom_histogram(bins = 5)
plot_grid(hist.age.boxcox, hist.bmi.boxcox, hist.children.boxcox, labels = "auto", align = "hv")
Rezultaty pokazały, że transformacja była możliwa jedynie dla
zmiennej bmi
, zatem zbudowano kolejny model uwzględniający
transformowane bmi
.
df.lm3 <- lm(charges ~ age + bmi.boxcox + children + smoker, data = df)
summary(df.lm3)
##
## Call:
## lm(formula = charges ~ age + bmi.boxcox + children + smoker,
## data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -11533 -2956 -1072 1504 29402
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -19116.15 1465.19 -13.047 < 2e-16 ***
## age 257.17 11.89 21.627 < 2e-16 ***
## bmi.boxcox 2023.70 170.57 11.865 < 2e-16 ***
## children 473.36 137.67 3.438 0.000603 ***
## smokeryes 23819.56 410.86 57.975 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6063 on 1333 degrees of freedom
## Multiple R-squared: 0.7501, Adjusted R-squared: 0.7494
## F-statistic: 1000 on 4 and 1333 DF, p-value: < 2.2e-16
par(mfrow = c(2, 2))
plot(df.lm3, pch = 16)
par(mfrow = c(1, 2))
cutoff <- 4 / (n - length(df.lm3$coefficients) - 2)
influencePlot(df.lm3, fill.alpha = 1, fill.col = "red")
## StudRes Hat CookD
## 439 -0.9414585 0.013121191 0.002357104
## 544 3.8938507 0.009465927 0.028674285
## 578 4.3519654 0.005062440 0.019017752
## 1086 -1.7523178 0.015378722 0.009577071
## 1301 4.9025126 0.004477112 0.021250713
plot(df.lm3, which = 4, cook.levels = cutoff, lwd = 2)
abline(h = cutoff, col = "red", lty = 2, lwd = 2)
df.lm3.AIC <- AIC(df.lm3)
df.lm3.AIC
## [1] 27111.7
anova(df.lm2, df.lm3)
## Analysis of Variance Table
##
## Model 1: charges ~ age + bmi + children + smoker
## Model 2: charges ~ age + bmi.boxcox + children + smoker
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 1333 4.9078e+10
## 2 1333 4.8993e+10 0 85440586
Ten model nie różni się od poprzednich.
We wszystkich 3 modelach widać było wiele obserwacji przekraczających dystans Cooka.
outlierTest(df.lm3)
## rstudent unadjusted p-value Bonferroni p
## 1301 4.902513 1.0625e-06 0.0014216
## 578 4.351965 1.4523e-05 0.0194320
infl <- influence.measures(df.lm3)$is.inf
data.frame(infl) %>%
mutate(isINF = if_any()) %>%
filter(isINF == T) %>%
length()
## [1] 10
Wykazano, że w ostatnim modelu mniej jest obserwacji odstających, jednak aż 10 wpływowych.
model <- c("df.lm1", "df.lm2", "df.lm3")
AIC.scores <- c(df.lm1.AIC, df.lm2.AIC, df.lm3.AIC)
ggplot() +
geom_col(aes(x = model, y = AIC.scores))
Powyższy wykres pokazuje, że nie zaobserwowano różnicy między ocenami modeli, dlatego wybrano model najprostszy, z najmniejszą liczbą zmiennych oraz brakiem transformowanych danych.
Przeprowadzona analiza wykazała, że istnieje istotny związek pomiędzy
zmiennymi age
, bmi
children
oraz
smoker
a wysokością opłacanej składki ubezpieczeniowej.
Zmienna region
nie wpływa na wysokość składki. Wydaje się,
że zmienna sex
wpływa na wysokość składki, ale nie jest
istotna statystycznie. W danych widoczne są wyraźne subpopulacje (np. 3
na wykresie age
vs charges
), co sugeruje, że
istnieje pewna zależność od zmiennej, która nie została uwzględniona w
analizie, być może związana z historią klienta. W każdym przypadku,
precyzyjne modelowanie składki na podstawie dostępnych danych jest
trudne, chyba że podzielimy klientów na konkretne grupy, co pozwoliłoby
na bardziej dokładne przewidywanie składek dla poszczególnych klientów w
każdej z tych grup. Jednak nie jest jasne, jak dokładnie podzielić
klientów na grupy, oprócz samego wyniku, który jest obecnie modelowany.
Przewidywania są dokładne tylko dla niższych składek, czyli dla większej
populacji klientów.