Clusterização no R: Como segmentar países de acordo com indicadores econômicos

Neste post, eu mostro como:
- Baixar dados de indicadores macroecômicos de todos os países usando a API do World Bank;
- Clusterizar países de acordo com esses indicadores usando o algoritmo k-means;
- O Brasil está mais próximo de Serra Leoa e Zimbábue que dos Estados Unidos e Noruega

library(WDI) # baixar os dados do World Bank
library(magrittr)
library(formattable)

Importação dos dados

Felizmente, o processo de importação dos dados do World Bank é feito de maneira automatizada pelo pacote WDI usando a função WDI(). Como é necessário inserir o código do indicador, usei a função WDIsearch() para buscar o código do indicador relacionado a, por exemplo, inflação:

WDIsearch("Inflation")
##      indicator           name                                   
## [1,] "FP.CPI.TOTL.ZG"    "Inflation, consumer prices (annual %)"
## [2,] "NY.GDP.DEFL.KD.ZG" "Inflation, GDP deflator (annual %)"

Portanto, o código do indicador de inflação é “FP.CPI.TOTL.ZG”. Repeti o mesmo para outros indicadores que escolhi para esta análise:

# lista de indicadores para baixar:
lista_indicadores <- c("FP.CPI.TOTL.ZG", # inflação (%)
                       "NY.GDP.PCAP.CD", # Pib per capita (USD)
                       "NY.GDP.MKTP.KD.ZG", # crescimento do PIB anual (%),
                       "SL.UEM.TOTL.ZS" # Desemprego (%)
)  
# Usei 2014 como ano de referência pois os resultados de alguns indicadores de 2015 ainda não foram disponibilizados
df <- WDI(indicator = lista_indicadores, country =  "all", start = 2014, end = 2014, extra = TRUE)
str(df)
## 'data.frame':    264 obs. of  14 variables:
##  $ iso2c            : chr  "1A" "1W" "4E" "7E" ...
##  $ country          : chr  "Arab World" "World" "East Asia & Pacific (excluding high income)" "Europe & Central Asia (excluding high income)" ...
##  $ year             : num  2014 2014 2014 2014 2014 ...
##  $ FP.CPI.TOTL.ZG   : num  2.67 2.51 3.86 2.53 6.82 ...
##  $ NY.GDP.PCAP.CD   : num  7446 10850 6305 9888 1497 ...
##  $ NY.GDP.MKTP.KD.ZG: num  2.91 2.83 6.76 2.18 7.06 ...
##  $ SL.UEM.TOTL.ZS   : num  11.49 5.76 4.39 7.71 3.88 ...
##  $ iso3c            : Factor w/ 248 levels "ABW","AFG","AGO",..: 6 243 NA NA 195 5 7 2 11 4 ...
##  $ region           : Factor w/ 8 levels "Aggregates","East Asia & Pacific (all income levels)",..: 1 1 NA NA 1 3 5 7 4 3 ...
##  $ capital          : Factor w/ 211 levels "","Abu Dhabi",..: 1 1 NA NA 1 10 2 80 167 191 ...
##  $ longitude        : Factor w/ 211 levels "","-0.126236",..: 1 1 NA NA 1 45 141 169 158 72 ...
##  $ latitude         : Factor w/ 211 levels "","0.20618","-0.229498",..: 1 1 NA NA 1 137 77 105 46 131 ...
##  $ income           : Factor w/ 7 levels "Aggregates","High income: nonOECD",..: 1 1 NA NA 1 2 2 5 7 7 ...
##  $ lending          : Factor w/ 5 levels "Aggregates","Blend",..: 1 1 NA NA 1 5 5 4 3 3 ...

O output acima mostra que o data frame não contém dados apenas de países mas também de unidades agregadas, como o mundo, mundo árabe, América Latina, etc. Por isso, removi as unidades agregadas:

df$region %<>% as.character
# remover agregados
df <- subset(df, region != "Aggregates")

Abaixo eu crio um novo dataframe apenas com as variáveis de interesse:

df2 <- df[, lista_indicadores]
row.names(df2) <- df$country
colnames(df2) <- c("inflacao", "pib_per_capita", "crescimento_pib", "desemprego")
summary(df2)
##     inflacao       pib_per_capita     crescimento_pib    desemprego    
##  Min.   :-1.5092   Min.   :   312.8   Min.   :-6.553   Min.   : 0.100  
##  1st Qu.: 0.6085   1st Qu.:  1937.1   1st Qu.: 1.529   1st Qu.: 4.748  
##  Median : 2.6378   Median :  6234.4   Median : 3.284   Median : 6.859  
##  Mean   : 3.9875   Mean   : 16021.2   Mean   : 3.374   Mean   : 9.105  
##  3rd Qu.: 5.3502   3rd Qu.: 18697.0   3rd Qu.: 5.227   3rd Qu.:11.818  
##  Max.   :62.1686   Max.   :179478.6   Max.   :10.300   Max.   :31.334  
##  NA's   :34        NA's   :19         NA's   :21       NA's   :27

Duas observações importantes sobre o output acima:
- Para facilitar a interpretação dos resultados da análise, transformei a taxa de desemprego em taxa de emprego, pois assim temos três indicadores que. quanto maior seus valores, mais pujante é a Economia de seus países;
- Alguns países não contém dados para alguns dos indicadores. Não há informação, por exemplo, sobre desemprego em 38 países.

Para resolver o problema dos valores ausentes (os NA), poderia ser aplicada uma técnica robusta, mas como esta é uma análise simples ou optei por remover os países que tinham algum dado faltando.

df2 <- na.omit(df2)
df2$desemprego <- 100 - df2$desemprego
names(df2)[4] <- "emprego"

Clusterização

Para usar o algoritmo k-means para clusterizar os países, é necessário:
- Calcular a distância (dissimilaridade) entre os países;
- Escolher o número de clusteres.

Para o cálculo da distância, temos um problema: as escalas das colunas são diferentes. Enquanto o PIB per capita é dado em dólares por pessoa e vão de 255 a 116,613, os outros são dados em porcentagem. Se não for feita nenhuma transformação dos dados, o PIB per capita terá um peso muito maior na clusterização dos dados que os outros indicadores.

Por isso, é necessário convertes todos os indicadores a uma escala única de média 0:

df2_escala <- scale(df2)
# Conferindo o output para o Brasil
df2_escala["Brazil", ]
##        inflacao  pib_per_capita crescimento_pib         emprego 
##       0.5197757      -0.1461311      -1.1241216       0.3173180

Na nova escala, temos que o Brasil apresenta inflação acima da média, PIB per capita abaixo da média, Crescimento do PIB abaixo da média (e olha que isso foi em 2014…) e taxa de emprego acima da média.

A determinação da quantidade de clusteres não segue uma regra pré-definida e deve ser pensada pelo responsável pela análise. Cada projeto de clusterização tem suas próprias particularidades. Contudo, alguns métodos analíticos podem ajudar nessa escolha, seja pela minização da soma dos quadrados dos clusteres ou pelo auxílio visual de um dendograma.

Para determinar o número de clusteres pelo primeiro método, observe o gráfico abaixo:

# referencia: http://www.statmethods.net/advstats/cluster.html
wss <- (nrow(df2_escala)-1)*sum(apply(df2_escala,2,var))
for (i in 2:15) wss[i] <- sum(kmeans(df2_escala,
   centers=i)$withinss)
plot(1:15, wss, type="b", xlab="Número of Clusters",
  ylab="Soma dos quadrados dentro dos clusteres") 

A soma dos quadrados dos clusteres se mantem estável a partir de 8 segmentos. Contudo, é preciso pensar qual a interpretação que teríamos disso. Quer dizer, posso dizer que dividi os dados em 8 clusteres, mas… e daí? O que seria aprendido por meio desses 8 clusteres?

Pelo segundo método, um dendograma é criado:

dendo <- df2_escala %>% dist %>% hclust
plot(dendo)
rect.hclust(dendo, k = 3, border = "blue")
rect.hclust(dendo, k = 4, border = "red")

A posição de cada país no dendograma é determinada pela dissimilaridade entre cada um dos outros países. Veja que a opção de 4 segmentos divide um dos segmentos da opção de 3 ao meio. Portanto, 4 parece ser uma boa escolha para a quantidade de clusteres do modelo desta análise.

Por exemplo, esta é a distância entre o Brasil e alguns outros países:

df2_escala[c("Brazil", "Sierra Leone", "Zimbabwe", "Norway", "United States"),] %>% dist
##                 Brazil Sierra Leone Zimbabwe   Norway
## Sierra Leone  1.787662                               
## Zimbabwe      1.695590     1.258069                  
## Norway        4.101143     4.610387 4.490002         
## United States 2.312514     2.753254 2.520473 2.014109

Dá para ver que o Brasil tem uma distância euclidiana de 1,90 em relação a Serra Leoa, 2,06 ao Zimbábue, 2,33 aos Estados Unidos e 4,06 a Noruega. Ou seja, levando em conta os indicadores macroeconômicos considerados nesta análise, é possível dizer que o Brasil é mais similar com países miseráveis do que com países ricos (Veja como os EUA são menos distantes em relação a Noruega do que ao Zimbábue).

Podemos também ver qual a distribuição do grau de dissimularidade do Brasil com o resto do mundo:

mat_brasil <- df2_escala %>% dist(diag = TRUE, upper = TRUE) %>% as.matrix
# 5 países com menor dissimilaridade
mat_brasil[, "Brazil"] %>% sort() %>% head(6)
##             Brazil Russian Federation           Suriname 
##          0.0000000          0.4111801          0.6143772 
##              Chile  Equatorial Guinea        Afghanistan 
##          0.6877354          0.7004780          0.7478158
# 5 países com MAIOR dissimilaridade
mat_brasil[, "Brazil"] %>% sort() %>% tail(5)
##    Namibia     Norway     Malawi Luxembourg      Sudan 
##   4.058707   4.101143   4.129854   5.473961   6.380094

O resultado dos 5 países mais distantes do Brasil é curioso: dentre eles, há 2 países ricos (Qatar e Luxemburgo) e três pobres (Malawi, Mauritânia e Sudão). Ou seja, não é necessariamente verdade que o Brasil é mais similar a países pobres da África que países ricos. Esse é o tipo de coisa que, se eu fosse um jornalista sensacionalista, omitiria.

Brincadeiras a parte, já podemos pular para a parte de criar os segmentos:

# fixar uma seed para garantir a reproducibilidade da análise:
set.seed(123) 
# criar os clusteres
lista_clusteres <- kmeans(df2_escala, centers = 4)$cluster
# função customizada para calcular a média dos indicadores para cada cluster
cluster.summary <- function(data, groups) {
  x <- round(aggregate(data, list(groups), mean), 2)
  x$qtd <- as.numeric(table(groups))
  # colocar coluna de quantidade na segunda posição
  x <- x[, c(1, 6, 2, 3, 4, 5)]
  return(x)
}

(tabela <- cluster.summary(df2, lista_clusteres))
##   Group.1 qtd inflacao pib_per_capita crescimento_pib emprego
## 1       1  97     3.62        6046.01            4.57   93.81
## 2       2   7    21.29        3026.91            1.85   92.65
## 3       3  30     1.74       54577.23            1.89   94.34
## 4       4  34     2.45        8977.34            1.76   79.76

Para melhorar a apresentação visual do output acima, usei o pacote formattable junto com uma função que criei para colorir de verde o valor caso seja superior ou igual à média do indicador e vermelho caso contrário.

colorir.valor <- function(x) ifelse(x >= mean(x), style(color = "green"), style(color = "red"))

nome_colunas <-  c("Cluster", "Quantidade de países do cluster", "Taxa de Inflação (%)",
                "PIB Per Capita (US$)","Crescimento anual do PIB (%)", "Taxa de Emprego (%)")
  
formattable(
  tabela,
  list(
    pib_per_capita = formatter("span", style = x ~ colorir.valor(x)),
    crescimento_pib = formatter("span", style = x ~ colorir.valor(x)),
    emprego = formatter("span", style = x ~ colorir.valor(x))
  ),  col.names = nome_colunas, format = "markdown", pad = 0
  )
Cluster Quantidade de países do cluster Taxa de Inflação (%) PIB Per Capita (US$) Crescimento anual do PIB (%) Taxa de Emprego (%)
1 97 3.62 6046.01 4.57 93.81
2 7 21.29 3026.91 1.85 92.65
3 30 1.74 54577.23 1.89 94.34
4 34 2.45 8977.34 1.76 79.76

Temos, então, 4 grupos de países distintos:
- Cluster 1: Inflação acima da média, PIB per capita abaixo, crescimento acima, emprego acima: países em desenvolvimento;
- Cluster 2: Inflação abaixo da média, PIB per capita muito acima, crescimento abaixo, emprego acima: países ricos;
- Cluster 3: Inflação abaixo da média, PIB per capita abaixo, crescimento abaixo, semprego acima: países relativamente pobres, piores que os do Cluster 1;
- Cluster 4: Inflação abaixo da média, PIB per capita abaixo, crescimento abaixo, emprego muito abaixo: países pobres.

Para finalizar, qual é o cluster do Brasil e quais os outros países que estão no mesmo segmento?

df2$cluster <- lista_clusteres
df2["Brazil",]
##        inflacao pib_per_capita crescimento_pib emprego cluster
## Brazil 6.332092       12026.62       0.5039618  93.188       1
cl_brasil <- df2["Brazil", ]$cluster

x <- df2[df2$cluster == cl_brasil, ]

x[order(-x$pib_per_capita),] %>% knitr::kable()
inflacao pib_per_capita crescimento_pib emprego cluster
Korea, Rep. 1.2747997 27811.3664 3.3414478 96.500 1
Malta 0.3115001 26180.9260 8.3056338 94.196 1
Bahrain 2.6511955 24983.3790 4.3498423 98.811 1
Saudi Arabia 2.6705256 24575.4030 3.6524817 94.280 1
Slovenia 0.2000749 24020.6729 3.1062802 90.332 1
Estonia -0.1448155 19941.4553 2.8229070 92.648 1
Czech Republic 0.3371869 19744.5586 2.7151161 93.892 1
Uruguay 8.8773533 16737.8983 3.2387912 93.540 1
Lithuania 0.1037899 16554.9714 3.4950162 89.302 1
Latvia 0.6085193 15725.0137 2.1199188 89.154 1
Chile 4.3950000 14817.3778 1.9096934 93.610 1
Poland 0.1069519 14341.6705 3.2825701 91.010 1
Russian Federation 7.8298397 14125.9061 0.7314582 94.844 1
Hungary -0.2223151 14117.9767 4.0473212 92.275 1
Kazakhstan 6.7183066 12806.5651 4.2000000 94.939 1
Panama 2.6378294 12593.7370 6.0533341 95.180 1
Turkey 8.8545727 12127.2252 5.1666907 90.120 1
Brazil 6.3320923 12026.6173 0.5039618 93.188 1
Malaysia 3.1746032 11183.9619 6.0121665 97.130 1
Costa Rica 4.5153127 10647.4418 3.6568036 90.380 1
Mexico 4.0186172 10452.2777 2.2653339 95.169 1
Mauritius 3.2176877 10153.9382 3.7445744 92.281 1
Romania 1.0689610 10020.2773 3.0763023 93.198 1
Suriname 3.3897457 9564.4064 0.3636006 93.060 1
Lebanon 0.7497186 8161.4614 1.8000000 93.770 1
Colombia 2.8778103 7913.3834 4.3936083 90.848 1
Azerbaijan 1.3850289 7891.2998 2.0000000 95.090 1
Maldives 2.1201131 7716.2040 5.9973264 94.790 1
China 2.0003449 7683.5020 7.2976660 95.407 1
Peru 3.2260468 6491.0525 2.3543330 95.924 1
Ecuador 3.5731279 6432.2165 3.9927085 96.200 1
Dominican Republic 2.9986423 6268.6921 7.6089648 85.500 1
Thailand 1.8958901 5941.8407 0.9145191 99.160 1
Algeria 2.9164064 5470.8510 3.7891212 89.400 1
Fiji 0.5403289 5046.0373 5.4464694 91.085 1
Belize 1.2013996 4852.2237 4.0810340 88.400 1
Paraguay 5.0288277 4712.8227 4.7223337 93.990 1
Angola 7.2795615 4709.3120 4.8044727 93.196 1
Georgia 3.0688121 4429.6501 4.6233317 87.650 1
Tonga 2.5108763 4192.3498 2.0646846 94.924 1
Mongolia 13.0246481 4181.5833 7.8852258 92.055 1
Samoa -0.4068161 4178.9734 1.1961473 91.280 1
Jordan 2.8915663 4066.9408 3.0963303 88.100 1
Guyana 0.9207697 4030.8023 3.8409153 88.173 1
El Salvador 1.1057751 3988.7719 1.4254757 94.079 1
Sri Lanka 2.7632866 3820.5410 4.9607192 95.600 1
Guatemala 3.4183617 3687.7638 4.1744006 97.090 1
Indonesia 6.3949254 3491.5959 5.0066684 94.060 1
Egypt, Arab Rep. 10.1458006 3327.7542 2.9159119 86.830 1
Nigeria 8.0573826 3221.6781 6.3097183 95.200 1
Morocco 0.4354565 3154.5135 2.5511096 90.100 1
Vanuatu 0.7988638 3148.3651 2.3310062 94.703 1
Bolivia 5.7835637 3124.0003 5.4605698 96.500 1
Congo, Rep. 0.0771605 2910.5202 6.7799410 89.944 1
Philippines 4.1044776 2842.9384 6.1452988 93.410 1
Bhutan 8.2065451 2522.7960 5.7454552 97.370 1
Moldova 5.0887858 2244.7638 4.7999997 96.140 1
Honduras 6.1292493 2242.7119 3.0580806 94.500 1
Papua New Guinea 5.2079395 2182.7166 8.5339018 97.420 1
Vietnam 4.0858999 2052.3191 5.9836546 98.130 1
Lao PDR 4.1352264 2017.5633 7.6135165 98.672 1
Nicaragua 6.0357919 1975.4647 4.7854601 94.727 1
Sao Tome and Principe 6.9984994 1821.8787 6.2076817 86.528 1
Djibouti 1.3418649 1740.9150 6.0001943 93.439 1
Zambia 7.8068755 1738.0882 4.6958264 92.274 1
India 6.6495002 1573.1181 7.5052202 96.470 1
Cote d’Ivoire 0.4530305 1569.9283 8.7940774 90.623 1
Cameroon 1.9479483 1441.1401 5.9269650 95.902 1
Kenya 6.8774981 1335.0646 5.3518399 88.185 1
Mauritania 3.5352189 1326.6688 5.5795439 89.933 1
Pakistan 7.1916712 1316.9810 4.6747080 94.400 1
Kyrgyz Republic 7.5342473 1279.7698 4.0240386 91.950 1
Myanmar 5.4744647 1262.8938 7.9912433 99.200 1
Timor-Leste 0.4435484 1153.5157 5.8611362 96.864 1
Tajikistan 6.1044277 1104.4590 6.6992507 89.268 1
Cambodia 3.8552386 1098.6871 7.0715254 99.900 1
Bangladesh 6.9911653 1084.5654 6.0610931 95.761 1
Senegal -1.0797448 1052.4439 4.3110510 89.637 1
Zimbabwe -0.2172862 1027.4075 2.7652707 94.869 1
Chad 1.6808359 1025.9985 6.8999850 94.228 1
Tanzania 6.1316143 950.3743 6.9651333 97.900 1
Benin -1.0857445 943.6866 6.3575210 98.957 1
Mali 0.8950092 825.5730 7.0433562 91.800 1
Uganda 3.0762851 719.1727 5.2468190 98.088 1
Sierra Leone 4.6454620 708.4395 4.5567724 97.200 1
Rwanda 1.7841004 706.5700 7.6196102 96.591 1
Nepal 8.3679788 706.2387 5.9889847 97.000 1
Burkina Faso -0.2580895 705.1464 4.1859447 96.731 1
Guinea-Bissau -1.5092446 642.6256 2.5410751 93.441 1
Togo 0.1867161 620.1318 5.8717263 93.183 1
Afghanistan 4.6043340 612.0697 1.3125309 91.551 1
Ethiopia 7.3918145 571.1623 10.2574930 95.019 1
Guinea 9.7139773 561.0997 0.3999986 93.048 1
Liberia 9.8263580 458.4652 0.7011416 96.377 1
Madagascar 6.0805955 452.4632 3.3158541 98.554 1
Niger -0.9245447 430.6046 7.0497979 97.534 1
Burundi 4.3798400 312.7490 4.6609182 98.432 1

Dá para perceber que existe um problema com nosso resultado: No mesmo segmento, estão presentes a Coreia do Sul e países como Haiti e Zimbábue. Isso pode ser explicado por uma série de razões, como:
- O número e perfil dos indicadores macroeconômicos escolhidos não é bom o suficiente para determinar uma segmentação eficiente dos países;
- O número de clusteres deveria ser maior;
- Deveriam ser feitas apenas interações (escolhendo valores diferentes como argumento de set.seed()) - O erro se deve a um erro aleatório, também chamado de ruído, do algoritmo k-means. Afinal de contas, como sabemos, nenhum modelo é perfeito.

 
comments powered by Disqus