No post Captura de dados da Corrida de São Silvestre com Python—Parte 1 eu levanto uma questão: Como o clima influencia o desempenho dos corredores de rua? Nesse post eu apresento como os dados dos campeões da corrida de São Silvestre foram capturados do site da corrida utilizando Python e em seguida transformados em um arquivos CSV.

Agora vou mostrar como o arquivo CSV foi utilizado, como os dados foram limpos e como foram formatados para que eu tenha os tipos corretos para a análise dos dados. Neste post eu uso R ao invés de Python, sem nenhuma razão específica.

Montando o ambiente

Para fazer esta análise eu carreguei os seguintes pacotes:

library(ggplot2)
library(dplyr)
library(tidyr)
library(lubridate)

O ggplot2 é para fazer gráficos, dplyr e tidyr para tratar os datasets e lubridate para manipulação de data e hora. Pessoalmente acho o dplyr excepcional para o manejo com os dados, ele impõe uma forma fluida de pensar nas colunas do dataset como variáveis e a partir daí todo o processo de manipulação de dados se torna simples.

Carregando os dados

Vamos começar carregando o arquivo saosilvestre.csv.

library(RCurl)
csv_file <- getURL('https://raw.githubusercontent.com/wilsonfreitas/saosilvestre/master/saosilvestre.csv')
ss <- read.csv(text=csv_file, header=TRUE, stringsAsFactor=FALSE)
head(ss)
##                nome           pais corrida  ano  horario        tempo
## 1      Dawit Admasu Eti<U+00F3>pia      90 2014 09:00:00 00:45:04.000
## 2  Ymer Wude Ayalew Eti<U+00F3>pia      90 2013 08:40:00 00:50:43.000
## 3     Edwin Kipsang  Qu<U+00EA>nia      89 2013 09:00:00 00:43:47.000
## 4      Nancy Kipron  Qu<U+00EA>nia      89 2013 08:40:00 00:51:58.000
## 5     Edwin Kipsang  Qu<U+00EA>nia      88 2012 09:00:00 00:44:05.000
## 6 Maurine Kipchumba  Qu<U+00EA>nia      88 2012 08:40:00 00:51:42.000
##   percurso
## 1    15000
## 2    15000
## 3    15000
## 4    15000
## 5    15000
## 6    15000
##                                                                                                largada
## 1 Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Ministro Rocha Azevedo (sentido Consola<U+00E7><U+00E3>o).
## 2 Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Ministro Rocha Azevedo (sentido Consola<U+00E7><U+00E3>o).
## 3 Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Ministro Rocha Azevedo (sentido Consola<U+00E7><U+00E3>o).
## 4 Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Ministro Rocha Azevedo (sentido Consola<U+00E7><U+00E3>o).
## 5                                               Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Frei Caneca.
## 6                                               Av. Paulista, pr<U+00F3>ximo <U+00E0> Rua Frei Caneca.
##                                                                                                  chegada
## 1 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.
## 2 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.
## 3 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.
## 4 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.
## 5 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.
## 6 Av. Paulista, 900, em frente ao Edif<U+00ED>cio da Funda<U+00E7><U+00E3>o C<U+00E1>sper L<U+00ED>bero.

Eu não vou utilizar as colunas largada, chegada e horario e como elas poluem a vizualização do dataset eu decidi removê-las:

ss <- ss %>% select(-largada, -chegada, -horario)
head(ss)
##                nome           pais corrida  ano        tempo percurso
## 1      Dawit Admasu Eti<U+00F3>pia      90 2014 00:45:04.000    15000
## 2  Ymer Wude Ayalew Eti<U+00F3>pia      90 2013 00:50:43.000    15000
## 3     Edwin Kipsang  Qu<U+00EA>nia      89 2013 00:43:47.000    15000
## 4      Nancy Kipron  Qu<U+00EA>nia      89 2013 00:51:58.000    15000
## 5     Edwin Kipsang  Qu<U+00EA>nia      88 2012 00:44:05.000    15000
## 6 Maurine Kipchumba  Qu<U+00EA>nia      88 2012 00:51:42.000    15000

Muito melhor!

Importante: o operador %>% é um pipe e compõe as sucessivas chamadas de funções para transformações em um dataset. O retorno da primeira expressão é atribuído ao primeiro argumento da função na segunda expressão, e caso houvessem mais expressões esse comportamento se repetiria até encontrar a última expressão e retornar o seu resultado para o chamador. Este operador, que é extensamente utilizado em conjunto com as funções do dplyr, tem origem no pacote magrittr.

Limpando e formatando os dados

Para ter o dataset pronto para uso eu preciso formatar os dados e corrigir os erros. No processo de análise eu encontrei alguns erros e como não será possível reproduzir este processo aqui eu vou listar os erros e apresentar as soluções.

Corrigindo o ano

O ano da corrida número 90 está errado. Este erro pode ser observado olhando os dados, veja na listagem abaixo onde eu uso a função filter do dplyr para selecionar os registros em que a coluna corrida é 90.

ss %>% filter(corrida == 90)
##               nome           pais corrida  ano        tempo percurso
## 1     Dawit Admasu Eti<U+00F3>pia      90 2014 00:45:04.000    15000
## 2 Ymer Wude Ayalew Eti<U+00F3>pia      90 2013 00:50:43.000    15000

Para corrigir isso é necessário definir para 2014 o ano dos registros que possuem corrida igual a 90.

ss <- ss %>% mutate(ano=ifelse(corrida == 90, 2014, ano))

Apenas para ter certeza vamos verificar os dados após a correção.

##               nome           pais corrida  ano        tempo percurso
## 1     Dawit Admasu Eti<U+00F3>pia      90 2014 00:45:04.000    15000
## 2 Ymer Wude Ayalew Eti<U+00F3>pia      90 2014 00:50:43.000    15000

Ótimo, ano corrigido!

Corrigindo o pais

Na coluna pais há registros mau preenchidos, alguns registros estão preenchidos errados e também há registros vazios. Vou começar com os registros errados, eles estão preenchidos com um hífen - e devem ser substituídos pelos países corretos.

ss %>% filter(pais == '-')
##                        nome pais corrida  ano        tempo percurso
## 1 Marizete de Paula Rezende    -      78 2002 00:54:02.000    15000
## 2            Lydia Cheromei    -      76 2000 00:50:33.000    15000

O pais do primeiro registro é Brasil e para o segundo é Quênia. Para corrigir esse erro eu utilizo a função grep para identificar os registros errados e substituir a coluna pais pelos valores comentados, mas mantendo a ordem correta, que eu consigo saber observando a listagem acima.

idx <- grep('^-', ss$pais)
ss[idx[1], 'pais'] <- 'Brasil'
ss[idx[2], 'pais'] <- 'Quênia'

Há também um registro com pais vazio.

ss %>% filter(pais == '')
##            nome pais corrida  ano        tempo percurso
## 1 Alfredo Gomes            1 1925 00:23:10.000     6200

O pais para este registro é Brasil e como é apenas 1 fica fácil corrigir com dplyr::mutate.

ss <- ss %>% mutate(pais=ifelse(pais == '', 'Brasil', pais))

Pronto, pais corrigido.

Corrigindo o percurso

Há 1 registro com erro na coluna percurso, e esse foi tricky para descobrir. Fazendo o gráfico de percurso por ano posso observar que a partir da década de 90 os percursos se mantém constantes em 15 km.

ggplot(data=ss, aes(x=ano, y=percurso)) + geom_point(size=2) + geom_line()

plot of chunk ano-percurso

Entretanto, logo após o ano 2000 há um ponto que caí vertiginosamente de 15 km para aproximadamente a metade. Observando o gráfico vejo que há variações semelhantes, porém isso pode me levar a questionar os demais valores. Por isso eu somente consegui identificar este erro quando criei a variável pace que mede a quantidade de tempo que um corredor leva para percorrer 1 km. Eu converto a variável tempo que é um character para difftime que é um objeto que representa intervalos de tempo. Pessoalmente acho difftime um nome miserável de ruim, mas no R algumas coisas são assim mesmo, meio feias ;). Depois dessa conversão eu consigo fazer as operações aritiméticas com difftime necessárias para o cálculo do pace.

ss <- ss %>% mutate(tempo=as.difftime(tempo), pace=1000*tempo/percurso)

Fazendo o gráfico do pace dos campeões por corrida temos:

ggplot(data=ss, aes(x=ano, y=as.numeric(pace))) + geom_point(size=2) + geom_line()

plot of chunk ano-pace

Bingo! É possível notar que no mesmo ano que no gráfico anterior há um outliar. Os paces de campeões são abaixo de 4 min/km, em média, de forma que um pace de 6 min/km é implausível. Para corrigir este registro vou assumir que ele é 15 km, como os demais em anos próximos. Dessa forma, será necessário atualizar o percurso e calcular novamente o pace. Para corrigir o percurso vou utilizar as operações de indexação no data.frame junto com a função which e para recalcular o pace eu utilizo a mesma expressão de antes (com mutate).

ss[which(ss$pace > 4),]$percurso <- 15000
ss <- ss %>% mutate(pace=1000*tempo/percurso)

Gerando novamente o gráfico dos paces observo que o erro está corrigido.

plot of chunk ano-pace-corrigido

Importante: é necessário converter o pace, que é um difftime, para numeric para construir o gráfico com ggplot2.

Importante: notei no gráfico dos paces que nos anos recentes há dois níveis distintos de pace. Isso refere-se aos paces masculino e feminino que vamos classificar em breve.

Classificando masculino e feminino: criando sexo

Como observei nos gráficos de pace, há uma diferença entre os paces: masculino e feminino, de forma que é interessante que eu consiga classificar os corredores quanto ao sexo. Eu sei que o tempo masculino é menor que o feminino, ou seja, um campeão do sexo masculino percorre a distância da prova em menos tempo do que uma campeã. Então eu posso assumir que nos anos em que há apenas 1 ocorrência, o sexo do campeão é masculino, e quando houverem 2 ocorrências o menor pace é do sexo masculino e o maior é do feminino. Também estou assumindo que não há provas com mais de 2 gêneros. Bem, posto isso a estratégia para classificar os campeões pelo sexo é:

  1. ordenar por ano e tempo
  2. agrupar por ano
  3. criar a variável sexo considerando sexo masculino quando a contagem do grupo for 1 e (masculino, feminino), nesta ordem, quando a contagem do grupo for diferente de 1

Parece meio confuso mas com dplyr isso tudo pode ser resolvido em meia dúzia de linhas.

ss <- ss %>%
  arrange(ano, tempo) %>%
  group_by(ano) %>%
  mutate(sexo=if (n() == 1) 'masculino' else c('masculino', 'feminino')) %>%
  ungroup %>%
  as.data.frame
tail(ss)
##                  nome           pais corrida  ano            tempo
## 125     Edwin Kipsang  Qu<U+00EA>nia      88 2012 44.08333333 mins
## 126 Maurine Kipchumba  Qu<U+00EA>nia      88 2012 51.70000000 mins
## 127     Edwin Kipsang  Qu<U+00EA>nia      89 2013 43.78333333 mins
## 128      Nancy Kipron  Qu<U+00EA>nia      89 2013 51.96666667 mins
## 129      Dawit Admasu Eti<U+00F3>pia      90 2014 45.06666667 mins
## 130  Ymer Wude Ayalew Eti<U+00F3>pia      90 2014 50.71666667 mins
##     percurso             pace      sexo
## 125    15000 2.938888889 mins masculino
## 126    15000 3.446666667 mins  feminino
## 127    15000 2.918888889 mins masculino
## 128    15000 3.464444444 mins  feminino
## 129    15000 3.004444444 mins masculino
## 130    15000 3.381111111 mins  feminino

Eu executo um tail para apresentar os resultados porque os dados foram ordenados por ano e portanto os últimos registros são os que apresentam provas com os 2 sexos. Para verificar se a classificação está correta vou fazer um gráfico do tempo por ano agrupando por sexo para ilustrar a classificação dos campeões. Mas antes vou criar mais uma coluna chamada contagem, com a contagem de ocorrências da corrida, essa variável me ajuda a filtar os resultados para apresentar apenas as corridas em que houveram 2 campeões, pois dessa forma eu consigo comparar os níveis de pace por sexo.

ss <- ss %>%
  group_by(ano) %>%
  mutate(contagem=n()) %>%
  ungroup %>%
  as.data.frame
ggplot(data=subset(ss, contagem == 2),
       aes(x=ano, y=as.numeric(tempo), group=sexo, colour=sexo)) +
  geom_point(size=2)

plot of chunk ano-tempo-masc-fem

Este gráfico me intriga porque a é possível observar 3 níveis distintos para o comportamento dos tempos dos campeões. Isso é um reflexo do percurso da prova, pois o tamanho do percurso aumentou ao longo dos anos até estacionar em 15 km. Outro gráfico semelhante ao anterior, mas apresentando percurso por ano agrupando por sexo, pode mostrar isso melhor.

ggplot(data=subset(ss, contagem == 2),
       aes(x=ano, y=percurso, group=sexo, colour=sexo)) +
  geom_point(size=2)

plot of chunk ano-percurso-masc-fem

Em todos as provas, exceto na segunda ocorrência do gráfico acima, os percursos masculinos e femininos são iguais. Este é um forte indício de que o percurso dessa prova para o sexo feminino também está errado.

head(subset(ss, contagem == 2))
##                  nome            pais corrida  ano            tempo
## 51        Victor Mora Col<U+00F4>mbia      51 1975 23.21666667 mins
## 52 Christa Valensieck        Alemanha      51 1975 28.65000000 mins
## 53     Edmundo Warnke           Chile      52 1976 23.83333333 mins
## 54 Christa Valensieck        Alemanha      52 1976 28.60000000 mins
## 55  Domingo Tibaduiza Col<U+00F4>mbia      53 1977 23.91666667 mins
## 56       Loa Olafsson       Dinamarca      53 1977 27.25000000 mins
##    percurso             pace      sexo contagem
## 51     8900 2.608614232 mins masculino        2
## 52     8900 3.219101124 mins  feminino        2
## 53     7300 3.264840183 mins masculino        2
## 54     8900 3.213483146 mins  feminino        2
## 55     8900 2.687265918 mins masculino        2
## 56     8900 3.061797753 mins  feminino        2

Listando o dataset observo que os registros da corrida 52 possuem os diferentes percursos, dessa forma vou assumir que os 2 deveriam ser 8900 m. Identificado o problema vou seguir com a mesma solução, selecionar os registros da corrida 52 e definir o percurso para 8900 m.

ss[which(ss$corrida == 52),]$percurso <- 8900
ss <- ss %>% mutate(pace=1000*tempo/percurso)

Para encerrar as dúvidas vou empilhar os gráficos de pace e tempo por ano, para avaliar se as hipóteses assumidas são pertinentes.

library(gridExtra)
ss_mw <- subset(ss, contagem == 2)

plot_pace <- ggplot(data=ss_mw, aes(x=ano, y=as.numeric(pace), group=sexo, colour=sexo)) +
  geom_point(size=2)
plot_time <- ggplot(data=ss_mw, aes(x=ano, y=as.numeric(tempo), group=sexo, colour=sexo)) +
  geom_point(size=2)

grid.arrange(plot_pace, plot_time, nrow=2)

plot of chunk ano-pace-tempo-masc-fem

Neste gráfico observo 2 regimes de pace contra 3 regimes de tempo. Há 3 regimes de tempo porque o tempo é diretamente proporcional ao percurso, maior o percurso, maior o tempo, e no período avaliado temos 3 percursos: 8900 m, 13 km e 15 km. Por outro lado os 2 regimes de pace são curiosos pois indicam a relação do pace com o tamanho do percurso, ou seja, maior o percurso, maior o pace, o que é esperado, dado que é difícil para um corredor manter o mesmo nível de esforço em provas de diferentes distância. No entanto, a diferença de 2 km entre os percursos de 13 km e 15 km não influencia o pace dos campeões.

Outra coisa, estes gráficos me fazem desconfiar de que a diferença do desempenho entre os sexos se mantém constante, em média, indicando algum tipo de estacionariedade, mas ainda seriam necessários mais gráficos e alguns testes estatísticos para comprovar isso.

Trabalho concluído

Bem, acredito aqui que o trabalho de limpeza e formatação está concluído. Pude passar por diversos problemas oriundos de uma manutenção pobre dos dados. O conteúdo das páginas não é confiável e vê-se que a atualização é manual e sem verificações. No entanto, este é o trabalho de quem tem que garimpar dados na Internet, é necessário criar formas de verificação e validação das hipóteses assumidas. Ainda falta fazer a análise dos dados para tentar responder a pergunta de como o clima pode influenciar o desempenho dos corredores de rua, mas isso fica pra outro post. Por ora vou salvar estes dados em um arquivo csv para usar na continuação deste trabalho.