1. Introdução - O significado dos dados

1.1. Antes de começar

satisfacoes = read_csv("https://raw.githubusercontent.com/cienciadedados-ufcg/inferencia-ismir/master/data/satisfacoes.csv",
                       col_types = "cdcc")

Os dados fonte desse documento são dados sobre a preferência de usuários quanto ao tipo de mêcanismo usado para a montagem de uma playlist em Jukeboxes. Foram dados carregados de um CSV que pode ser encontrados nesse link. Para mais referencias, acessar esse link.

Observação: Durante esse documento todas as descobertas feitas estarão grifadas em negrito.

1.2. Conhecendo os Dados

No experimento de coleta desses dados, usuários foram divididos em 3 grupos e avaliaram 5 formas de organizar uma playlist. A avaliação é uma nota de 1 à 5, onde quanto maior a nota, mais o usuário está satisfeito com aquela maneira de organizar a playlist.

glimpse(satisfacoes)
Rows: 115
Columns: 4
$ user_id      <chr> "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",…
$ satisfaction <dbl> 2.0, 3.0, 1.5, 1.0, 2.0, 2.5, 2.0, 3.0, 2.0, 2.0, 2.0, 2…
$ scenario     <chr> "baseline", "baseline", "baseline", "baseline", "baselin…
$ group        <chr> "3", "1", "2", "2", "1", "1", "3", "3", "2", "1", "3", "…

O que cada coluna significa:

  • user_id: O id do usuário que fez a avaliação.
  • satisfaction: A avaliação do usuário, ou seja, o quão satisfeito ele está com o método que está sendo avaliado.
  • scenario: O método que está sendo avaliado.
  • group: O grupo que o usuário fez parte durante a avaliação.
satisfacoes %>% 
  select(scenario) %>% 
  unique() %>% 
  rename(
    "Métodos" = scenario
  )

Como os métodos estão divididos:

  • baseline: Uma fila simples. Quando uma nova música é adicionada à playlist, ela é apenas posta no final da fila.
  • combined: Todos os métodos são colocados juntos.
  • like/dislike: Apresenta um recurso visual que indica aos usuários se os outros usuários estão gostando (Like) ou não (Dislike) das músicas que estão sendo adicionadas.
  • skip: Permite que os usuários decidam se pulam uma música da playlist. Se determinada quantidade de usuários votarem que querem pular essa música, a música que está sendo tocada é pausada e a proxima da fila é reproduzida.
  • up/downvoting: De acordo com as avaliações dos usuários, as músicas mais bem avaliadas são tocadas primeiro e as piores avaliadas são tocadas depois. Esse metodo é similar ao usado pelo Reddit. O usuário pode escolher entre avaliar bem (Upvoting) e avaliar mal (Downvoting) uma música. O que define a avaliação geral da música é o somatorio de todas as avaliações dos usuários que estão escutando a playlist.

2. Primeira análise - A amostra de dados

O que interessa principalmente na análise que vamos fazer é chegar em qual o melhor método para a montagem de uma playlist colaborativa. Para isso iremos analisar em como a avaliação do usuário (satisfaction) está relacionada com o método usado (scenario).

Importante, é sabido que os grupos não afetam a metrica de satisfação, por isso, vamos ignorar esse dado nessa análise. Em casos que não se tem essa informação, deve-se levar em consideração que algum vies pode ter ocorrido por estar em um certo grupo.

Além disso, todos os métodos possuem a mesma quantidade de avaliações:

satisfacoes %>% 
  group_by(scenario) %>% 
  count() %>% 
  rename(
    "Método" = scenario,
    "Quantidade de avaliações" = n
  )

2.1. Como estão distribuidas as avaliações nesse conjunto de dados?

Para se ter uma primeira ideia de como esses dois dados estão relacionados, podemos fazer um plot de pontos para visualizarmos qual o formato da distribuição das avaliações (satisfaction) nos métodos usados (scenario):

satisfacoes %>% 
  ggplot(aes(
    x = scenario,
    y = satisfaction,
    color = scenario 
  )) +
  geom_quasirandom() +
  geom_violin(fill = NA) +
  scale_color_discrete(name = "Métodos") +
  labs(
    title = "Distribuição de pontos das avaliações de acordo com o método usado",
    x = "Método usado",
    y = "Avaliação do usuário"
  )

Nessa distribuição, podemos ver que existem alguns métodos que se destacam como os melhores, como o up/downvoting que obteve as melhores notas nesse experimento. Temos também métodos que se destacam de forma negativa, como por exemplo o baseline, sendo o único método que recebeu notas 1.

Além disso, podemos dividir visualmente os métodos em dois grupos:

  1. Melhores avaliados: like/dislike, combined e up/downvoting
  2. Piores avaliados: baseline e skip

2.2. Como a média dessas avaliações se comporta?

Para termos uma ideia melhor da avaliação desses métodos de forma geral, podemos usar uma médida de centro, como a média, para comprimir esses dados:

satisfacoes %>% 
  group_by(scenario) %>% 
  summarise(media = mean(satisfaction)) %>% 
  ggplot(aes(
    x = reorder(scenario, media),
    y = media,
    color = reorder(scenario, media)
  )) +
  geom_point(
    size = 5, 
    alpha = 0.75
  ) +
  ylim(1,5) +
  scale_color_discrete(name = "Métodos") +
  labs(
    title = "Distribuição das médias das avaliações de acordo com o método usado",
    x = "Método usado",
    y = "Média das avaliações dos usuário"
  )

Como podemos ver, a partir dessas médias, podemos estabelecer uma hierarquia de preferencia para a maioria do usuários do experimento quanto ao método usado:

  1. up/downvoting
  2. combined
  3. like/dislike
  4. skip
  5. baseline

Essa hierarquia vale apenas para a amostra de dados que possuimos, ainda não temos nenhuma informação quanto a população


3. Aprofundando a análise - Utilizando ICs e Bootstraping para realizar inferências

Para aprofundarmos a analise, precisamos fazer uma generalização, ou seja, precisamos saber se realmente há uma preferencia quanto ao método usado, tendo em vista a população.

Para isso, podemos calcular alguns intervalos de confiança com bootstraping para as médias e ver se o comportamento da amostra se perpetua para a população.

Intervalos de confiança são estimativas para uma certa médida desconhecida da população, ou seja, é um intervalo que, a partir de um nível de confiança, determina quais os valores que uma médida pode assumir. O nível de confiança é o nível de certeza que o valor daquela médida na população, estará no intervalo.

Bootstraping é o método de criar novas amostras que representam bem a população a partir de reamostragem com reposição. A amostra usada para realização desse método é uma amostra que representa a população bem.

O nome desse processo é inferência estatística. Análisar a amostra e a partir dela, deduzir com um grau de confiança informações sobre a população.

3.1. Utilizando métodos de inferência

Primeiro temos que definir, qual o nivel de confiança desejado e quantas amostras queremos usar em cada boostraping:

nivel_confianca = 0.95
paste("Nivel de confiança: ", nivel_confianca)
[1] "Nivel de confiança:  0.95"
quantidade_amostras = 4000
paste("Quantidade de amostras: ", quantidade_amostras)
[1] "Quantidade de amostras:  4000"

Também precisamos definir como calcularemos a métrica que será medida a cada bootstraping. Para isso, definimos uma função que recebe o dataset (d), os indices das linhas que foram escolhidas para a nova amostra (i) e qual cálculo será feito:

calcula_media = function(d, i) {
  d %>% 
    slice(i) %>% 
    summarise(media = mean(satisfaction)) %>% 
    pull(media)
}

Por fim, podemos definir uma função que calcula o intervalo de confiança da média da avaliação de um método na população. Fazemos isso com um bootstrap a partir do nome daquele método (scenario_bootstrap).

Observação: Estamos utilizando a biblioteca boot para gerar o bootstrap e a biblioteca broom para facilitar a integração com outras bibliotecas do conjunto tidyverse.

bootstrap_satisfacao = function(scenario_bootstrap) {
  satisfacoes_scenario = satisfacoes %>% 
    filter(scenario == scenario_bootstrap)
  
   satisfacoes_scenario %>% 
    boot(statistic = calcula_media, R = quantidade_amostras) %>% 
    tidy(conf.level = nivel_confianca,
         conf.int = TRUE)
}

A partir dessa função podemos calcular o intervalo de confiança das médias de todos os métodos na população.

Primeiro, identificamos quais o métodos foram usados:

tipos_scenario = satisfacoes %>% 
  select(scenario) %>% 
  unique() 

tipos_scenario %>% 
  rename(
    "Métodos" = scenario
  )

E depois geramos as médias para cada um desses métodos:

ics_scenarios = tipos_scenario %>% 
  cbind(map_df(tipos_scenario$scenario, ~ bootstrap_satisfacao(.))) %>% 
  select(-bias, -std.error)

ics_scenarios %>% 
  rename(
    'Método' = scenario,
    'Média' = statistic,
    'Limite inferior' = conf.low,
    'Limite superior' = conf.high,
  )

Além das médias, podemos ver que obtemos o Limite inferior e o Limite superior. Esses limites são o que formam o intervalo de confiança.

Para visualizarmos como a média se comporta na população, podemos plotar num grafico, similar ao que fizemos para a média da amostra, a média da população resultante do bootstrap. Além da média, iremos plotar o intervalo de confiança, já que, a média real das avaliações da população está dentro desse intervalo em qualquer lugar com 95% de confiança.

ics_scenarios %>% 
  ggplot(aes(
    x = reorder(scenario, statistic),
    y = statistic,
    ymin = conf.low,
    ymax = conf.high,
    color = reorder(scenario, statistic)
  )) +
  geom_point(size = 4) +
  geom_errorbar() +
  ylim(1,5) +
  scale_color_discrete(name = "Métodos") +
  labs(
    title = "Intervalos de confiança a partir da avaliação de um método",
    x = "Método",
    y = "Avaliação"
  )

A partir dessa visualização podemos fazer algumas inferências sobre a população com um nível de confiança de 95%:

  • O método pior avaliado foi ou o baseline ou o skip.
  • O método melhor avaliado foi ou o up/downvoting ou o combined.
  • Os métodos up/downvoting, combined e like/dislike foram melhores avaliados em relação aos métodos baseline e skip.

Também existem inferências que não podemos fazer:

  • O método que foi melhor avaliado foi o up/downvoting: Não podemos afirmar isso pois existe uma interseção entre o intervalo de confiança desse método e do método combined, ou seja, existe a possibilidade de combined ser melhor avaliado do que o up/downvoting.
  • O método que foi pior avaliado foi o baseline: Não podemos afirmar isso pelo menos motivo já apresentado para a afirmação de qual método foi melhor avaliado. O intervalo de confiança do método baseline possui uma interseção com o intervalo de confiança do método skip.

4. Aprimorando ainda mais a analise - Usando diferenças de médias como métrica do bootstrap

Não conseguimos descobrir qual o método mais bem avaliado pelos usuários na população usando a média. No entanto, podemos fazer um bootstrap comparando a diferença das médias entre dois métodos.

4.1. Tentando descobrir qual é o

Para isso, precisamos de todas as combinações de como podemos comparar os métodos, ou seja, todos os pares de métodos possiveis:

Observação: No código abaixo foi utilizado permutações para no futuro visualizar essas diferenças num gráfico.

combinacoes_scenario = tipos_scenario %>%
  pull(scenario) %>%
  gtools::permutations(n = 5, r = 2)

combinacoes_scenario
      [,1]            [,2]           
 [1,] "baseline"      "combined"     
 [2,] "baseline"      "like/dislike" 
 [3,] "baseline"      "skip"         
 [4,] "baseline"      "up/downvoting"
 [5,] "combined"      "baseline"     
 [6,] "combined"      "like/dislike" 
 [7,] "combined"      "skip"         
 [8,] "combined"      "up/downvoting"
 [9,] "like/dislike"  "baseline"     
[10,] "like/dislike"  "combined"     
[11,] "like/dislike"  "skip"         
[12,] "like/dislike"  "up/downvoting"
[13,] "skip"          "baseline"     
[14,] "skip"          "combined"     
[15,] "skip"          "like/dislike" 
[16,] "skip"          "up/downvoting"
[17,] "up/downvoting" "baseline"     
[18,] "up/downvoting" "combined"     
[19,] "up/downvoting" "like/dislike" 
[20,] "up/downvoting" "skip"         

Além disso, precisamos de uma função que gera o bootstrap a partir de dois nomes de métodos:

bootstrap_diferenca = function(scenario1, scenario2) {
  calcula_diferenca = function(d, i) {
    medias = d %>%
      slice(i) %>%
      group_by(scenario) %>%
      summarise(media = mean(satisfaction))

    scenario1_media = medias %>% filter(scenario == scenario1) %>% pull(media)
    scenario2_media = medias %>% filter(scenario == scenario2) %>% pull(media)

    scenario2_media - scenario1_media
  }

  satisfacoes_scenario = satisfacoes %>%
    filter(scenario %in% c(scenario1, scenario2))

  satisfacoes_scenario %>%
    boot(statistic = calcula_diferenca, R = quantidade_amostras) %>%
    tidy(conf.level = nivel_confianca,
         conf.int = TRUE)
}

Agora, podemos gerar as diferenças a partir das combinações e da função que faz o bootstrap. Passamos através de um map todas as combinações possiveis e como função para ser executada pelo map, uma função que é responsavel por criar a linha da tabela com a identificação dos métodos comparados e o boostrap referente aquela comparação:

build_line = function(comb_number) {
  scenario1 = combinacoes_scenario[comb_number, 1]
  scenario2 = combinacoes_scenario[comb_number, 2]

  cbind(
    tibble(scenario1, scenario2),
    bootstrap_diferenca(scenario1, scenario2)
  )
}

diferencas = map_df(1:NROW(combinacoes_scenario), ~ build_line(.))

diferencas %>%
  select(-bias, -std.error) %>%
  rename(
    "Método 1" = scenario1,
    "Método 2" = scenario2,
    "Média" = statistic,
    "Limite inferior" = conf.low,
    "Limite superior" = conf.high
  )

Uma pergunta relevante é se existe alguma interseção dos intervalos de confiança com o 0, ou seja, se existe algum caso em que não se pode identificar a preferência de um método em relação a outro. Para responder essa pergunta, podemos ver se os sinais do limite superior e do limite superior são diferentes, se forem, isso quer dizer que o intervalo de confiança contem o 0 e com isso não podemos identificar a preferencia:

n_intersecoes = diferencas %>%
  filter(sign(conf.high) != sign(conf.low)) %>% 
  NROW()

paste("Número de interseções: ", n_intersecoes)
[1] "Número de interseções:  0"

Nesse caso, não vemos nenhuma interseção e podemos inferir a preferencia de um método em relação a outro em todos os casos.

Podemos também visualizar essas diferenças em um gráfico:

diferencas %>%
  ggplot(aes(
    x = scenario2,
    y = statistic,
    ymin = conf.low,
    ymax = conf.high,
    color = scenario2
  )) +
  geom_pointrange() +
  geom_hline(yintercept = 0, linetype = "dashed", alpha = 0.5) +
  coord_flip() +
  ylim(-3, 3) +
  facet_wrap(~ scenario1) +
  labs(
    x = '',
    y = ''
  )

Por fim, podemos fazer um ranking de métodos tendo em vista a quantidade de métodos que ele teve uma avaliação melhor.

Fazemos isso comparando o sinal de um dos limites do intervalo de confiança - Não importa qual limite pois os dois limites de todas as comparações tem sinal igual - e vendo se aquele sinal é negativo. Se for negativo, quer dizer que o método base é mais eficiente que o método que está se comparando. Se não for, quer dizer que o método comparado é mais eficiente.

Com isso, fazemos a comparação entre todos os métodos, fazemos a contagem de quantos métodos aquele se sobressai e criamos o ranking:

calcula_sobressai = function(diferencas, scenario) {
  diferencas %>%
    filter(scenario1 == scenario) %>%
    filter(sign(conf.low) == -1) %>%
    NROW()
}

tipos_scenario %>%
  mutate(sobressai = map_dbl(scenario, ~ calcula_sobressai(diferencas, .))) %>%
  mutate(posicao = 5 - sobressai) %>% 
  arrange(posicao) %>% 
  rename(
    "Método" = scenario,
    "Posição" = posicao,
    "Se sobressai em X casos" = sobressai
  )

Como podemos ver, conseguimos inferir para a população qual método seria o mais bem avaliado. Nesse caso, o mais bem avaliado seria o up/downvoting.