As Olimpíadas estão acabando… e, além de saudades e histórias de superação, cada Olimpíada deixa uma enorme quantidade de dados! Nesse artigo, vamos ver como podemos usar o Pandas para analisar parte desses dados, verificando a evolução do tempo dos atletas em provas de atletismo.
Tópicos
Obtendo os dados
O primeiro passo é obter os dados para análise. Basicamente, há três formas:
- obtenção direta a partir de uma fonte confiável e que disponibiliza os dados adequadamente;
- obtenção direta a partir de uma fonte confiável mas que não disponibiliza os dados adequadamente;
- obtenção indireta, via terceiros.
Uma fonte confiável seria a página https://olympics.com/, do Comitê Olímpico Internacional. No entanto, ela não disponibiliza os dados de uma forma adequada como, por exemplo, tabelas que poderiam ser baixadas diretamente do site. Nem fornece uma API para requisições. Logo, uma forma de obter seria fazendo scraping dos dados, usando, por exemplo, Beautiful Soup ou Selenium. Ou mesmo o Pandas, como já mostrei nesse artigo. Mas seria necessário um estudo da estrutura das páginas, especialmente porque o site faz uso de dados gerados dinamicamente.
Agora, pense… a Olimpíada é o evento esportivo mais importante do mundo, certamente muitas pessoas já buscaram os dados pelos mais diversos motivos e, eventualmente, algumas podem ter disponibilizado em algum formato adequado aos nossos propósitos. E o lugar onde provavelmente esses dados seriam encontrados é no Kaggle, um site voltado para data science e machine learning.
Experimente buscar “olympics” no Kaggle e verá uma quantidade razoável de bases de dados a respeito. No caso, escolhi essa aqui, que, segundo o criador, possui os dados de atletismo do site https://olympics.com/ de 1896 até 2016. Obviamente que precisaremos testar a consistência dos dados, visto que estamos obtendo indiretamente, mas nada muito diferente do que já faríamos mesmo obtendo diretamente. Observe também que a base de dados não inclui 2020, o que é bastante razoável tendo em vista que escrevo ainda durante o período das Olimpíadas. Mas, como veremos, não é muito difícil inserir dados atuais, ao menos em pequena escala.
Feitas essas considerações, vamos ao código. Comecemos importando o pandas
e verificando o arquivo dos dados que, no caso, foi salvo em uma pasta chamada data
sob o nome results.csv
. O caminho para o arquivo foi atribuído à uma variável file
:
import pandas as pd
file = 'data/results.csv'
Olhando as 5 primeiras e as 5 últimas linhas do arquivo, temos uma ideia geral de sua organização:
!head -5 data/results.csv
Gender,Event,Location,Year,Medal,Name,Nationality,Result M,10000M Men,Rio,2016,G,Mohamed FARAH,USA,25:05.17 M,10000M Men,Rio,2016,S,Paul Kipngetich TANUI,KEN,27:05.64 M,10000M Men,Rio,2016,B,Tamirat TOLA,ETH,27:06.26 M,10000M Men,Beijing,2008,G,Kenenisa BEKELE,ETH,27:01.17
!tail -5 data/results.csv
W,Triple Jump Women,Athens,2004,S,Hrysopiyi DEVETZI,GRE,15.25 W,Triple Jump Women,Athens,2004,B,Tatyana LEBEDEVA,RUS,15.14 W,Triple Jump Women,Atlanta,1996,G,Inessa KRAVETS,UKR,15.33 W,Triple Jump Women,Atlanta,1996,S,Inna LASOVSKAYA,RUS,14.98 W,Triple Jump Women,Atlanta,1996,B,Sarka KASPARKOVA,CZE,14.98
A coluna chamada Result no arquivo se refere aos tempos dos atletas, então acho que faz mais sentido ter nome Time. Uma inspeção visual no arquivo como um todo mostra que algumas linhas possuem ainda uma columa com informação sobre o vento durante a competição, mesmo essa coluna não sendo nomeada no início do arquivo. Mostro essa inspeção no vídeo no início do artigo. E o porquê há dados sobre vento será explicado no decorrer do artigo.
Assim, os seguintes nomes de coluna me parecem adequados:
columns = ('Gender', 'Event', 'Location', 'Year', 'Medal', 'Name', 'Nationality', 'Time', 'Wind')
Vamos, então, criar nosso dataframe do pandas com essas colunas, lembrando de ignorar a primeira linha do arquivo já que são os nomes de coluna que não queremos usar:
df = pd.read_csv(file, names=columns, skiprows=1)
df
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
0 | M | 10000M Men | Rio | 2016 | G | Mohamed FARAH | USA | 25:05.17 | NaN |
1 | M | 10000M Men | Rio | 2016 | S | Paul Kipngetich TANUI | KEN | 27:05.64 | NaN |
2 | M | 10000M Men | Rio | 2016 | B | Tamirat TOLA | ETH | 27:06.26 | NaN |
3 | M | 10000M Men | Beijing | 2008 | G | Kenenisa BEKELE | ETH | 27:01.17 | NaN |
4 | M | 10000M Men | Beijing | 2008 | S | Sileshi SIHINE | ETH | 27:02.77 | NaN |
… | … | … | … | … | … | … | … | … | … |
2389 | W | Triple Jump Women | Athens | 2004 | S | Hrysopiyi DEVETZI | GRE | 15.25 | NaN |
2390 | W | Triple Jump Women | Athens | 2004 | B | Tatyana LEBEDEVA | RUS | 15.14 | NaN |
2391 | W | Triple Jump Women | Atlanta | 1996 | G | Inessa KRAVETS | UKR | 15.33 | NaN |
2392 | W | Triple Jump Women | Atlanta | 1996 | S | Inna LASOVSKAYA | RUS | 14.98 | NaN |
2393 | W | Triple Jump Women | Atlanta | 1996 | B | Sarka KASPARKOVA | CZE | 14.98 | NaN |
2394 rows × 9 columns
A estrutura do dataframe parece ser bem autoexplicativa. Mas já podemos observar alguns detalhes. O formato do tempo dos atletas não é consistente. Nas primeiras linhas vemos no formato minutos:segundos.centésimos e, nas últimas linhas, vemos apenas segundos.centésimos. Veja, obviamente que do ponto de vista esportivo não faz sentido mostrar minutos em provas de curtas, mas isso pode dificultar a análise dos tempos caso se resolva fazer alguma análise que envolva mais de um tipo de evento. Não necessariamente isso é um problema, é uma observação.
Outra observação é que há redundância na informação de genêro, visto que há uma coluna para essa informação e os eventos também apresentam em seu nome.
Para aprofundar na nossa compreensão inicial dos dados, vamos usar os métodos info
e describe
. Ambos servem para obter informações sobre o dataframe:
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2394 entries, 0 to 2393 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 2394 non-null object 1 Event 2394 non-null object 2 Location 2394 non-null object 3 Year 2394 non-null int64 4 Medal 2394 non-null object 5 Name 2164 non-null object 6 Nationality 2394 non-null object 7 Time 2394 non-null object 8 Wind 12 non-null float64 dtypes: float64(1), int64(1), object(7) memory usage: 168.5+ KB
Vemos com o info
que o Pandas reconheceu a coluna de anos como sendo do tipo inteiro, e a de vento como float. Todas as demais são do tipo object
que, segundo a documentação do pacote, é o tipo para colunas com strings ou colunas com tipos misturados. A coluna de tempos ser do tipo object terá consequências mais para frente no artigo.
A coluna Wind
apresenta apenas 12 valores não nulos, indicando que boa parte dos registros está sem essa informação. Pode ser por realmente não ter tido vento nesses casos, ou seja, seria equivalente a zero, ou uma falta dessa informação no banco de dados por algum motivo.
Há também registros nulos na coluna Name
, vejamos como isso se reflete em nossa análise no decorrer do artigo.
df.describe()
Year | Wind | |
---|---|---|
count | 2394.000000 | 12.00000 |
mean | 1970.379282 | -0.02500 |
std | 34.711777 | 0.56909 |
min | 1896.000000 | -0.90000 |
25% | 1948.000000 | -0.15000 |
50% | 1976.000000 | 0.10000 |
75% | 2000.000000 | 0.22500 |
max | 2016.000000 | 0.60000 |
O método describe
quando usado sem nenhum parâmetro apresenta um breve resumo estatístico das colunas de tipo numérico. No caso, a de anos e a de vento. Observe que, aparentemente, os dados realmente são de 1896 a 2016, pois estes são os valores mínimo e máximo, respectivamente. Mas isso não garante que há realmente todos os anos olímpicos nesse intervalo. Para isso:
df['Year'].unique()
array([2016, 2008, 2000, 1992, 1984, 1976, 1968, 1960, 1952, 1936, 1928, 1920, 2012, 2004, 1996, 1980, 1972, 1964, 1956, 1948, 1932, 1924, 1912, 1908, 1900, 1904, 1896, 1988])
O método unique
apresenta os valores únicos em uma dada coluna. Observe que os anos estão fora de ordem, o que atrapalha um pouco a análise. Vamos ordenar de forma crescente com o método sort_values
:
df['Year'].sort_values().unique()
array([1896, 1900, 1904, 1908, 1912, 1920, 1924, 1928, 1932, 1936, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016])
Observe que faltam os anos de 1916, 1940 e 1944 nessa lista. Mas faz sentido, são anos de guerra mundial… sem olimpíadas.
O método describe
pode receber parâmetros, dentre eles o include
que permite forçar a presença de outros tipos. Vamos ver uma descrição dos tipos object
:
df.describe(include='object')
Gender | Event | Location | Medal | Name | Nationality | Time | |
---|---|---|---|---|---|---|---|
count | 2394 | 2394 | 2394 | 2394 | 2164 | 2394 | 2394 |
unique | 2 | 47 | 23 | 3 | 1681 | 97 | 1947 |
top | M | Marathon Men | London | B | Merlene OTTEY | USA | None |
freq | 1632 | 84 | 253 | 799 | 7 | 639 | 43 |
Observe que as informações mostradas mudaram. A linha de contagem é autoexplicativa e nos mostra uma informação que já tínhamos com o info
. A linha unique é similar ao método que utilizamos anteriormente, mostrando quantos valores únicos há em cada coluna. Os valores para gênero e para medalhas são fáceis de entender. No mais, descobrimos que há 47 eventos na base de dados, que 23 localidades já receberam as Olimpíadas, que atletas de 97 nacionalidades distintas já receberam medalhas no atletismo (ao menos até 2016) e que 2164 – 1681 = 483 atletas receberam mais de uma medalha.
A linha top apresenta os valores que mais se repetiram, enquanto que a linha freq mostra quantas vezes se repetiram os valores da linha top. Assim, vemos que o país que mais recebeu medalhas foram os Estados Unidos.
Mas precisamos tomar cuidado com a forma de apresentação das linhas top e freq, pois se houver mais de uma entrada que ocorrem em mesma quantidade, será apresentada apenas uma. Vamos procurar o atleta que mais ganhou medalhas de uma forma mais completa. Para isso, vamos mostrar o início da coluna de medalhas, em ordem decrescente, após agrupar os dados pelo nome dos atletas:
df.groupby(by='Name').count()['Medal'].sort_values(ascending=False).head()
Name Merlene OTTEY 7 Paavo NURMI 7 Tirunesh DIBABA 6 Usain BOLT 6 Irena KIRSZENSTEIN 6 Name: Medal, dtype: int64
Vemos que dois atletas receberam 7 medalhas no atletismo de acordo com a base de dados: Paavo Nurmi e Merlene Ottey, vale muito a leitura sobre eles. E ainda aparece o Bolt entre os primeiros, atleta já abordado aqui no site. No entanto, esses números não batem exatamente com os que aparecem nos links que coloquei para cada um deles. Isso pode ser um indício de problemas com a base de dados. Pode ser, por exemplo, erro de digitação nos nomes dos atletas ou uma base incompleta mesmo. Vamos seguir adiante, mas já com consciência desses indícios de problemas.
Perceba que só com alguns pequenos comandos já fomos capazes de encontrar informações relevantes. Acostume-se a fazer essa análise prévia com info
e describe
. Ajuda muito a entender a forma de apresentação dos dados, a ter uma ideia de sua completude e consistência e a direcionar análises posteriores.
Agora é o momento de escolher o que se deseja fazer. Há várias possibilidades mas, para esse artigo, resolvi focar nos 100 m para mulheres. Afinal, já fiz um artigo sobre os dados do Usain Bolt, então vamos dar um espaço para as meninas também, especialmente com o recorde olímpico quebrado agora em 2020.
Focando em um evento
Para filtrar os dados podemos usar o método loc
, buscando na coluna Event por 100M Women:
df.loc[df['Event'] == '100M Women']
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1687 | W | 100M Women | Rio | 2016 | G | Elaine THOMPSON | JAM | 10.71 | NaN |
1688 | W | 100M Women | Rio | 2016 | S | Tori BOWIE | USA | 10.83 | NaN |
1689 | W | 100M Women | Rio | 2016 | B | Shelly-Ann FRASER-PRYCE | JAM | 10.86 | NaN |
1690 | W | 100M Women | Beijing | 2008 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1691 | W | 100M Women | Beijing | 2008 | S | Sherone SIMPSON | JAM | 10.98 | NaN |
1692 | W | 100M Women | Beijing | 2008 | S | Kerron STEWART | JAM | 10.98 | NaN |
1693 | W | 100M Women | Sydney | 2000 | S | Ekaterini THANOU | GRE | 11.12 | NaN |
1694 | W | 100M Women | Sydney | 2000 | S | Tanya LAWRENCE | JAM | 11.18 | NaN |
1695 | W | 100M Women | Sydney | 2000 | B | Merlene OTTEY | JAM | 11.19 | NaN |
1696 | W | 100M Women | Barcelona | 1992 | G | Gail DEVERS | USA | 10.82 | NaN |
1697 | W | 100M Women | Barcelona | 1992 | S | Juliet CUTHBERT | JAM | 10.83 | NaN |
1698 | W | 100M Women | Barcelona | 1992 | B | Irina PRIVALOVA | EUN | 10.84 | NaN |
1699 | W | 100M Women | Los Angeles | 1984 | G | Evelyn ASHFORD | USA | 10.97 | NaN |
1700 | W | 100M Women | Los Angeles | 1984 | S | Alice BROWN | USA | 11.13 | NaN |
1701 | W | 100M Women | Los Angeles | 1984 | B | Merlene OTTEY | JAM | 11.16 | NaN |
1702 | W | 100M Women | Montreal | 1976 | G | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1703 | W | 100M Women | Montreal | 1976 | S | Renate STECHER | GDR | 11.13 | NaN |
1704 | W | 100M Women | Montreal | 1976 | B | Inge HELTEN | FRG | 11.17 | NaN |
1705 | W | 100M Women | Mexico | 1968 | G | Wyomia TYUS | USA | 11.0 | NaN |
1706 | W | 100M Women | Mexico | 1968 | S | Barbara FERRELL | USA | 11.1 | NaN |
1707 | W | 100M Women | Mexico | 1968 | B | Irena KIRSZENSTEIN | POL | 11.1 | NaN |
1708 | W | 100M Women | Rome | 1960 | G | Wilma RUDOLPH | USA | 11.0 | NaN |
1709 | W | 100M Women | Rome | 1960 | S | Dorothy HYMAN | GBR | 11.3 | NaN |
1710 | W | 100M Women | Rome | 1960 | B | Giuseppina LEONE | ITA | 11.3 | NaN |
1711 | W | 100M Women | Helsinki | 1952 | G | Marjorie JACKSON | AUS | 11.5 | NaN |
1712 | W | 100M Women | Helsinki | 1952 | S | Daphne HASENJAGER | RSA | 11.8 | NaN |
1713 | W | 100M Women | Helsinki | 1952 | B | Shirley STRICKLAND | AUS | 11.9 | NaN |
1714 | W | 100M Women | Berlin | 1936 | G | Helen STEPHENS | USA | 11.5 | NaN |
1715 | W | 100M Women | Berlin | 1936 | S | Stanislawa WALASIEWICZ | POL | 11.7 | NaN |
1716 | W | 100M Women | Berlin | 1936 | B | Käthe KRAUSS | GER | 11.9 | NaN |
1717 | W | 100M Women | Amsterdam | 1928 | G | Elizabeth ROBINSON | USA | 12.2 | NaN |
1718 | W | 100M Women | Amsterdam | 1928 | S | Fanny ROSENFELD | CAN | None | NaN |
1719 | W | 100M Women | Amsterdam | 1928 | B | Ethel SMITH | CAN | None | NaN |
1720 | W | 100M Women | London | 2012 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1721 | W | 100M Women | London | 2012 | S | Carmelita JETER | USA | 10.78 | NaN |
1722 | W | 100M Women | London | 2012 | B | Veronica CAMPBELL-BROWN | JAM | 10.81 | NaN |
1723 | W | 100M Women | Athens | 2004 | G | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1724 | W | 100M Women | Athens | 2004 | S | Lauryn WILLIAMS | USA | 10.96 | NaN |
1725 | W | 100M Women | Athens | 2004 | B | Veronica CAMPBELL-BROWN | JAM | 10.97 | NaN |
1726 | W | 100M Women | Atlanta | 1996 | G | Gail DEVERS | USA | 10.94 | NaN |
1727 | W | 100M Women | Atlanta | 1996 | S | Merlene OTTEY | JAM | 10.94 | NaN |
1728 | W | 100M Women | Atlanta | 1996 | B | Gwen TORRENCE | USA | 10.96 | NaN |
1729 | W | 100M Women | Moscow | 1980 | G | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1730 | W | 100M Women | Moscow | 1980 | S | Marlies OELSNER-GÃHR | GDR | 11.07 | NaN |
1731 | W | 100M Women | Moscow | 1980 | B | Ingrid AUERSWALD-LANGE | GDR | 11.14 | NaN |
1732 | W | 100M Women | Munich | 1972 | G | Renate STECHER | GDR | 11.07 | NaN |
1733 | W | 100M Women | Munich | 1972 | S | Raelene Ann BOYLE | AUS | 11.23 | NaN |
1734 | W | 100M Women | Munich | 1972 | B | Silvia CHIVAS BARO | CUB | 11.24 | NaN |
1735 | W | 100M Women | Tokyo | 1964 | G | Wyomia TYUS | USA | 11.4 | NaN |
1736 | W | 100M Women | Tokyo | 1964 | S | Edith MCGUIRE | USA | 11.6 | NaN |
1737 | W | 100M Women | Tokyo | 1964 | B | Ewa KLOBUKOWSKA | POL | 11.6 | NaN |
1738 | W | 100M Women | Melbourne / Stockholm | 1956 | G | Betty CUTHBERT | AUS | 11.5 | NaN |
1739 | W | 100M Women | Melbourne / Stockholm | 1956 | S | Christa STUBNICK | EUA | 11.7 | NaN |
1740 | W | 100M Women | Melbourne / Stockholm | 1956 | B | Marlene MATHEWS-WILLARD | AUS | 11.7 | NaN |
1741 | W | 100M Women | London | 1948 | G | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1742 | W | 100M Women | London | 1948 | S | Dorothy HALL | GBR | 12.2 | NaN |
1743 | W | 100M Women | London | 1948 | B | Shirley STRICKLAND | AUS | 12.2 | NaN |
1744 | W | 100M Women | Los Angeles | 1932 | G | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
1745 | W | 100M Women | Los Angeles | 1932 | S | Hilda STRIKE | CAN | 11.9 | NaN |
1746 | W | 100M Women | Los Angeles | 1932 | B | Wilhelmina VON BREMEN | USA | 12.0 | NaN |
Agora precisamos novamente escolher o que fazer, qual caminho de análise tomar. Poderíamos, por exemplo, avaliar a distância relativa entre os medalhistas de ouro, prata e bronze para verificar se os tempos estão ficando mais próximos no decorrer dos anos. Ou avaliar o ganho/perda de tempo entre cada edição do evento. São diversas possibilidades. Aqui, vou escolher uma análise relativamente simples, para que o artigo seja uma introdução ao pandas. Vamos avaliar a evolução dos tempos das medalhistas de ouro desde a primeira Olimpíada em que teve a competição de 100 m feminino até a edição de agora.
Portanto, precisamos adicionar mais uma condição ao nosso filtro, buscando por G (de gold, ouro em inglês) na coluna Medal. O símbolo & indica que ambas as condições devem ser satisfeitas:
df.loc[(df['Event'] == '100M Women') & (df['Medal'] == 'G')]
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1687 | W | 100M Women | Rio | 2016 | G | Elaine THOMPSON | JAM | 10.71 | NaN |
1690 | W | 100M Women | Beijing | 2008 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1696 | W | 100M Women | Barcelona | 1992 | G | Gail DEVERS | USA | 10.82 | NaN |
1699 | W | 100M Women | Los Angeles | 1984 | G | Evelyn ASHFORD | USA | 10.97 | NaN |
1702 | W | 100M Women | Montreal | 1976 | G | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1705 | W | 100M Women | Mexico | 1968 | G | Wyomia TYUS | USA | 11.0 | NaN |
1708 | W | 100M Women | Rome | 1960 | G | Wilma RUDOLPH | USA | 11.0 | NaN |
1711 | W | 100M Women | Helsinki | 1952 | G | Marjorie JACKSON | AUS | 11.5 | NaN |
1714 | W | 100M Women | Berlin | 1936 | G | Helen STEPHENS | USA | 11.5 | NaN |
1717 | W | 100M Women | Amsterdam | 1928 | G | Elizabeth ROBINSON | USA | 12.2 | NaN |
1720 | W | 100M Women | London | 2012 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1723 | W | 100M Women | Athens | 2004 | G | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1726 | W | 100M Women | Atlanta | 1996 | G | Gail DEVERS | USA | 10.94 | NaN |
1729 | W | 100M Women | Moscow | 1980 | G | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1732 | W | 100M Women | Munich | 1972 | G | Renate STECHER | GDR | 11.07 | NaN |
1735 | W | 100M Women | Tokyo | 1964 | G | Wyomia TYUS | USA | 11.4 | NaN |
1738 | W | 100M Women | Melbourne / Stockholm | 1956 | G | Betty CUTHBERT | AUS | 11.5 | NaN |
1741 | W | 100M Women | London | 1948 | G | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1744 | W | 100M Women | Los Angeles | 1932 | G | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
OK, o filtro parece fazer sentido, então vamos associar esse dataframe à uma variável de forma a ser possível fazer mais análises:
df_women_100m = df.loc[(df['Event'] == '100M Women') & (df['Medal'] == 'G')]
df_women_100m
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1687 | W | 100M Women | Rio | 2016 | G | Elaine THOMPSON | JAM | 10.71 | NaN |
1690 | W | 100M Women | Beijing | 2008 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1696 | W | 100M Women | Barcelona | 1992 | G | Gail DEVERS | USA | 10.82 | NaN |
1699 | W | 100M Women | Los Angeles | 1984 | G | Evelyn ASHFORD | USA | 10.97 | NaN |
1702 | W | 100M Women | Montreal | 1976 | G | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1705 | W | 100M Women | Mexico | 1968 | G | Wyomia TYUS | USA | 11.0 | NaN |
1708 | W | 100M Women | Rome | 1960 | G | Wilma RUDOLPH | USA | 11.0 | NaN |
1711 | W | 100M Women | Helsinki | 1952 | G | Marjorie JACKSON | AUS | 11.5 | NaN |
1714 | W | 100M Women | Berlin | 1936 | G | Helen STEPHENS | USA | 11.5 | NaN |
1717 | W | 100M Women | Amsterdam | 1928 | G | Elizabeth ROBINSON | USA | 12.2 | NaN |
1720 | W | 100M Women | London | 2012 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1723 | W | 100M Women | Athens | 2004 | G | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1726 | W | 100M Women | Atlanta | 1996 | G | Gail DEVERS | USA | 10.94 | NaN |
1729 | W | 100M Women | Moscow | 1980 | G | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1732 | W | 100M Women | Munich | 1972 | G | Renate STECHER | GDR | 11.07 | NaN |
1735 | W | 100M Women | Tokyo | 1964 | G | Wyomia TYUS | USA | 11.4 | NaN |
1738 | W | 100M Women | Melbourne / Stockholm | 1956 | G | Betty CUTHBERT | AUS | 11.5 | NaN |
1741 | W | 100M Women | London | 1948 | G | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1744 | W | 100M Women | Los Angeles | 1932 | G | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
Verificando a completude do banco de dados
Antes de continuar fazendo mais análises em cima dos dados, vamos verificar se estão completos e aparentemente coerentes. Vimos anteriormente que os anos de 1916, 1940 e 1944 não estão presentes na base de dados pois não ocorreram Olimpíadas nesses anos. Mas vamos ver se para os 100 m femininos a base está completa. Afinal, anteriormente não tínhamos uma análise tão granular, evento a evento, de forma que alguns podem estar incompletos mas não tínhamos como saber.
Seguindo o mesmo procedimento já feito, vamos ordenar a coluna de anos:
df_women_100m.sort_values(by='Year')
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1717 | W | 100M Women | Amsterdam | 1928 | G | Elizabeth ROBINSON | USA | 12.2 | NaN |
1744 | W | 100M Women | Los Angeles | 1932 | G | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
1714 | W | 100M Women | Berlin | 1936 | G | Helen STEPHENS | USA | 11.5 | NaN |
1741 | W | 100M Women | London | 1948 | G | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1711 | W | 100M Women | Helsinki | 1952 | G | Marjorie JACKSON | AUS | 11.5 | NaN |
1738 | W | 100M Women | Melbourne / Stockholm | 1956 | G | Betty CUTHBERT | AUS | 11.5 | NaN |
1708 | W | 100M Women | Rome | 1960 | G | Wilma RUDOLPH | USA | 11.0 | NaN |
1735 | W | 100M Women | Tokyo | 1964 | G | Wyomia TYUS | USA | 11.4 | NaN |
1705 | W | 100M Women | Mexico | 1968 | G | Wyomia TYUS | USA | 11.0 | NaN |
1732 | W | 100M Women | Munich | 1972 | G | Renate STECHER | GDR | 11.07 | NaN |
1702 | W | 100M Women | Montreal | 1976 | G | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1729 | W | 100M Women | Moscow | 1980 | G | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1699 | W | 100M Women | Los Angeles | 1984 | G | Evelyn ASHFORD | USA | 10.97 | NaN |
1696 | W | 100M Women | Barcelona | 1992 | G | Gail DEVERS | USA | 10.82 | NaN |
1726 | W | 100M Women | Atlanta | 1996 | G | Gail DEVERS | USA | 10.94 | NaN |
1723 | W | 100M Women | Athens | 2004 | G | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1690 | W | 100M Women | Beijing | 2008 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1720 | W | 100M Women | London | 2012 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1687 | W | 100M Women | Rio | 2016 | G | Elaine THOMPSON | JAM | 10.71 | NaN |
Algumas coisas interessantes já aparecem. A primeira edição que aparece no banco de dados é a de 1928 e isto está correto. A primeira edição feminina realmente foi em 1928, enquanto que a masculina ocorre desde 1896.
Agora, há duas ausências estranhas: 1988 e 2000. Vamos buscar tais anos no dataframe completo, começando pelo ano 2000:
df.loc[(df['Year'] == 2000) & (df['Event'] == '100M Women')]
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1693 | W | 100M Women | Sydney | 2000 | S | Ekaterini THANOU | GRE | 11.12 | NaN |
1694 | W | 100M Women | Sydney | 2000 | S | Tanya LAWRENCE | JAM | 11.18 | NaN |
1695 | W | 100M Women | Sydney | 2000 | B | Merlene OTTEY | JAM | 11.19 | NaN |
Observe que há, sim, registro do evento na base de dados, mas sem medalhista de ouro. E, por mais estranho que pareça, isto também está correto. A medalhista de ouro seria Marion Jones mas, devido ao uso de substâncias banidas do esporte, ela acabou perdendo a medalha e o Comitê Olímpico Internacional decidiu por não promover Ekaterini Thanou tendo em vista que esta também já esteve envolvida em casos de doping. É uma longa história que pode ser lida nos links que deixei aqui.
O que importa aqui é que a base de dados está coerente nesse ponto. Resta resolver o que fazer com essa informação. Como resolvemos filtrar apenas por medalhistas de ouro, realmente o ano 2000 ficaria sem registro de tempo. Uma outra abordagem seria considerar o tempo de Thanou, já que esse seria oficialmente o melhor tempo da final daquele ano, talvez colocando alguma indicação visual de que não se trata de uma medalhista de ouro. Tudo depende da história que se quer contar e do tipo de análise que se deseja fazer. Aqui, vou manter a decisão de incluir apenas medalhistas de ouro e, portanto, o ano 2000 ficará sem registro.
Vamos avaliar o ano de 1988 agora:
df.loc[(df['Year'] == 1988) & (df['Event'] == '100M Women')]
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind |
---|
Aqui o caso é diferente, não há registro do evento para 1988, o que está errado. Pode ser que esse ano esteja comprometido na base de dados, vamos verificar:
df.loc[(df['Year'] == 1988)]
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
423 | M | 20Km Race Walk Men | Seoul | 1988 | G | Jozef PRIBILINEC | TCH | 1:19:57 | NaN |
424 | M | 20Km Race Walk Men | Seoul | 1988 | S | Ronald WEIGEL | GDR | 1:20:00 | NaN |
425 | M | 20Km Race Walk Men | Seoul | 1988 | B | Maurizio DAMILANO | ITA | 1:20:14 | NaN |
911 | M | 50Km Race Walk Men | Seoul | 1988 | G | Vyacheslav IVANENKO | URS | 3:38:29 | NaN |
912 | M | 50Km Race Walk Men | Seoul | 1988 | S | Ronald WEIGEL | GDR | 3:38:56 | NaN |
913 | M | 50Km Race Walk Men | Seoul | 1988 | B | Hartwig GAUDER | GDR | 3:39:45 | NaN |
1058 | M | Decathlon Men | Seoul | 1988 | G | Christian SCHENK | GDR | 8488.0 | NaN |
1059 | M | Decathlon Men | Seoul | 1988 | S | Torsten VOSS | GDR | 8399.0 | NaN |
1060 | M | Decathlon Men | Seoul | 1988 | B | Dave STEEN | CAN | 8328.0 | NaN |
1426 | M | Marathon Men | Seoul | 1988 | G | Gelindo BORDIN | ITA | 2:10:32 | NaN |
1427 | M | Marathon Men | Seoul | 1988 | S | Douglas WAKIIHURI | KEN | 2:10:47 | NaN |
1428 | M | Marathon Men | Seoul | 1988 | B | Hussein AHMED SALAH | DJI | 2:10:59 | NaN |
2162 | W | Heptathlon Women | Seoul | 1988 | G | Jackie JOYNER | USA | 7291 P. | NaN |
2163 | W | Heptathlon Women | Seoul | 1988 | S | Sabine JOHN | GDR | 6897.0 | NaN |
2164 | W | Heptathlon Women | Seoul | 1988 | B | Anke BEHMER | GDR | 6858.0 | NaN |
2319 | W | Marathon Women | Seoul | 1988 | G | Rosa MOTA | POR | 2:25:40 | NaN |
2320 | W | Marathon Women | Seoul | 1988 | S | Lisa ONDIEKI | AUS | 2:25:53 | NaN |
2321 | W | Marathon Women | Seoul | 1988 | B | Katrin DÃRRE | GDR | 2:26:21 | NaN |
Realmente há algum problema na base de dados, faltam todos os eventos de curta distância. E, infelizmente, não há qualquer aviso a respeito no link do Kaggle da base de dados. Por isso é importante fazer esses testes de coerência. Veja, mesmo que os dados tivessem como origem direta o site oficial das Olimpíadas esse tipo de checagem é fundamental, pois erros acontecem. Aqui, pode ser que o site estivesse sem os dados na época onde foi feita a coleta ou então houve algum erro no algoritmo utilizado para fazer o scraping dos dados. Enfim, inúmeras possibilidades, o importante é que percebemos o problema e é simples de resolvê-lo.
Uma rápida busca no próprio site das Olimpíadas mostra que a medalhista de ouro de 1988 foi Florence Griffith Joyner, outra esportista cuja história é interessante (e curta…). Com os dados do site, podemos atualizar nosso dataframe, aproveitando também para adicionar a medalhista de ouro de 2020, Elaine Thompson. Para isso, vamos utilizar o método loc
, adicionando linhas após os registros já existentes:
df_women_100m.loc[len(df_women_100m.index)] = ['W', '100M Women', 'Seoul', 1988, 'G', 'Florence Griffith JOYNER', 'USA', 10.54, 3.0]
df_women_100m.loc[len(df_women_100m.index)] = ['W', '100M Women', 'Tokyo', 2020, 'G', 'Elaine THOMPSON', 'JAM', 10.61, -0.6]
df_women_100m
Gender | Event | Location | Year | Medal | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|---|---|---|
1687 | W | 100M Women | Rio | 2016 | G | Elaine THOMPSON | JAM | 10.71 | NaN |
1690 | W | 100M Women | Beijing | 2008 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1696 | W | 100M Women | Barcelona | 1992 | G | Gail DEVERS | USA | 10.82 | NaN |
1699 | W | 100M Women | Los Angeles | 1984 | G | Evelyn ASHFORD | USA | 10.97 | NaN |
1702 | W | 100M Women | Montreal | 1976 | G | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1705 | W | 100M Women | Mexico | 1968 | G | Wyomia TYUS | USA | 11.0 | NaN |
1708 | W | 100M Women | Rome | 1960 | G | Wilma RUDOLPH | USA | 11.0 | NaN |
1711 | W | 100M Women | Helsinki | 1952 | G | Marjorie JACKSON | AUS | 11.5 | NaN |
1714 | W | 100M Women | Berlin | 1936 | G | Helen STEPHENS | USA | 11.5 | NaN |
1717 | W | 100M Women | Amsterdam | 1928 | G | Elizabeth ROBINSON | USA | 12.2 | NaN |
1720 | W | 100M Women | London | 2012 | G | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1723 | W | 100M Women | Athens | 2004 | G | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1726 | W | 100M Women | Atlanta | 1996 | G | Gail DEVERS | USA | 10.94 | NaN |
1729 | W | 100M Women | Moscow | 1980 | G | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1732 | W | 100M Women | Munich | 1972 | G | Renate STECHER | GDR | 11.07 | NaN |
1735 | W | 100M Women | Tokyo | 1964 | G | Wyomia TYUS | USA | 11.4 | NaN |
1738 | W | 100M Women | Melbourne / Stockholm | 1956 | G | Betty CUTHBERT | AUS | 11.5 | NaN |
1741 | W | 100M Women | London | 1948 | G | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1744 | W | 100M Women | Los Angeles | 1932 | G | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
19 | W | 100M Women | Seoul | 1988 | G | Florence Griffith JOYNER | USA | 10.54 | 3.0 |
20 | W | 100M Women | Tokyo | 2020 | G | Elaine THOMPSON | JAM | 10.61 | -0.6 |
Aqui percebemos algo interessante. O tempo de Joyner em 1988 foi melhor que o de Thompson agora em 2020. Porém, como Joyner teve um grande vento de 3,0 m/s a seu favor, não foi considerado como recorde olímpico. O recorde olímpico anterior era da própria Joyner com o tempo de 10,62 s feito nas quartas de final da Olimpíada de 1988 com um vento favorável de 1,0 m/s e, portanto, dentro da margem aceita para registro de recordes.
Aliás, uma breve tangente sobre vento. O recorde mundial até hoje é novamente de Florence Joyner, com o tempo de 10,49 s feito nas seletivas para as Olimpíadas de 1988. O vento registrado oficialmente durante a prova foi de 0 m/s. No entanto, o tempo de Joyner foi tão chamativo que foram olhar com mais cuidado os registros do evento como um todo naquele dia. Em várias modalidades foram registrados ventos na casa de 5,0 m/s, de forma que se acredita que houve uma falha no anemômetro (medidor de velocidade de um fluido) utilizado na prova. De qualquer forma, o recorde foi mantido e, mesmo que fosse retirado, o segundo melhor tempo ainda é de Joyner, com 10,61 s sem assistência de vento. Esse tempo agora foi igualado por Thompson.
Novamente, aqui temos algo a se pensar. O objetivo é mostrar a evolução do tempo das medalhistas de ouro. Sob esse olhar, o tempo de Joyner é melhor, mas houve auxílio de vento. Cabe a quem está contando a história resolver como lidar com essa situação. Se vai deixar isso evidente de alguma forma, ou vai substituir pelo melhor tempo sem auxílio. Enfim, escolhas a serem feitas e que vamos lidar com elas adiante.
Arrumando o dataframe
Agora que temos confiança na consistência de nossos dados e ganhamos mais conhecimento sobre os mesmos, vamos arrumar a casa. Podemos retirar colunas redundantes:
df_women_100m = df_women_100m.drop(columns=['Gender', 'Event', 'Medal'])
df_women_100m
Location | Year | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|
1687 | Rio | 2016 | Elaine THOMPSON | JAM | 10.71 | NaN |
1690 | Beijing | 2008 | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
1696 | Barcelona | 1992 | Gail DEVERS | USA | 10.82 | NaN |
1699 | Los Angeles | 1984 | Evelyn ASHFORD | USA | 10.97 | NaN |
1702 | Montreal | 1976 | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
1705 | Mexico | 1968 | Wyomia TYUS | USA | 11.0 | NaN |
1708 | Rome | 1960 | Wilma RUDOLPH | USA | 11.0 | NaN |
1711 | Helsinki | 1952 | Marjorie JACKSON | AUS | 11.5 | NaN |
1714 | Berlin | 1936 | Helen STEPHENS | USA | 11.5 | NaN |
1717 | Amsterdam | 1928 | Elizabeth ROBINSON | USA | 12.2 | NaN |
1720 | London | 2012 | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
1723 | Athens | 2004 | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
1726 | Atlanta | 1996 | Gail DEVERS | USA | 10.94 | NaN |
1729 | Moscow | 1980 | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
1732 | Munich | 1972 | Renate STECHER | GDR | 11.07 | NaN |
1735 | Tokyo | 1964 | Wyomia TYUS | USA | 11.4 | NaN |
1738 | Melbourne / Stockholm | 1956 | Betty CUTHBERT | AUS | 11.5 | NaN |
1741 | London | 1948 | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
1744 | Los Angeles | 1932 | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
19 | Seoul | 1988 | Florence Griffith JOYNER | USA | 10.54 | 3.0 |
20 | Tokyo | 2020 | Elaine THOMPSON | JAM | 10.61 | -0.6 |
E, também, arrumar o índice dos registros, que ainda se referem a números do dataframe completo original, e aproveitar para já deixar o dataframe ordenado por ano para facilitar a compreensão:
df_women_100m = df_women_100m.sort_values(by='Year').reset_index(drop=True)
df_women_100m
Location | Year | Name | Nationality | Time | Wind | |
---|---|---|---|---|---|---|
0 | Amsterdam | 1928 | Elizabeth ROBINSON | USA | 12.2 | NaN |
1 | Los Angeles | 1932 | Stanislawa WALASIEWICZ | POL | 11.9 | NaN |
2 | Berlin | 1936 | Helen STEPHENS | USA | 11.5 | NaN |
3 | London | 1948 | Fanny BLANKERS-KOEN | NED | 11.9 | NaN |
4 | Helsinki | 1952 | Marjorie JACKSON | AUS | 11.5 | NaN |
5 | Melbourne / Stockholm | 1956 | Betty CUTHBERT | AUS | 11.5 | NaN |
6 | Rome | 1960 | Wilma RUDOLPH | USA | 11.0 | NaN |
7 | Tokyo | 1964 | Wyomia TYUS | USA | 11.4 | NaN |
8 | Mexico | 1968 | Wyomia TYUS | USA | 11.0 | NaN |
9 | Munich | 1972 | Renate STECHER | GDR | 11.07 | NaN |
10 | Montreal | 1976 | Annegret RICHTER-IRRGANG | FRG | 11.08 | NaN |
11 | Moscow | 1980 | Lyudmila KONDRATYEVA | URS | 11.06 | NaN |
12 | Los Angeles | 1984 | Evelyn ASHFORD | USA | 10.97 | NaN |
13 | Seoul | 1988 | Florence Griffith JOYNER | USA | 10.54 | 3.0 |
14 | Barcelona | 1992 | Gail DEVERS | USA | 10.82 | NaN |
15 | Atlanta | 1996 | Gail DEVERS | USA | 10.94 | NaN |
16 | Athens | 2004 | Yuliya NESTSIARENKA | BLR | 10.93 | NaN |
17 | Beijing | 2008 | Shelly-Ann FRASER-PRYCE | JAM | 10.78 | NaN |
18 | London | 2012 | Shelly-Ann FRASER-PRYCE | JAM | 10.75 | NaN |
19 | Rio | 2016 | Elaine THOMPSON | JAM | 10.71 | NaN |
20 | Tokyo | 2020 | Elaine THOMPSON | JAM | 10.61 | -0.6 |
Da nossa análise anterior, já vimos que a coluna Time é vista pelo Pandas como sendo do tipo object
, o que está errado. Precisamos transformá-la em um tipo numérico para poder continuar com nossas análises. Mas, antes disso, preste um pouco mais de atenção nos dados de tempo. Percebe alguma diferença na forma de apresentação ao longo do tempo?
Os registros até 1968 apresentam apenas uma casa decimal. Uma história detalhada do uso de medidores automáticos de tempo no atletismo pode ser lida aqui mas, resumidamente, tempos com precisão na casa dos centésimos realmente só começaram a ser utilizados olimpicamente em 1972. No entanto, desde as Olimpíadas de 1928 já se utilizavam registradores automáticos, juntamente com registros manuais. Nas Olimpíadas da década de 60 os resultados oficiais foram obtidos com registradores automáticos, mas publicados oficialmente arredondados para a primeira casa decimal. Apenas em 1972 passaram a publicar oficialmente os valores até os centésimos. Mais uma informação interessante que pode ser obtida após olhar a forma como os dados foram apresentados.
O Pandas possui dois métodos muito úteis para lidar com registros de tempo, o to_datetime
e o to_timedelta
que certamente seriam necessários para lidar com os tempos das provas mais longas, onde o formato de relógio Hora:minuto:segundo.milisegundo é utilizado. No entanto, no nosso caso, os valores são similares o suficiente ao formato de float, de forma que podemos arriscar uma conversão direta para esse tipo, pois certamente dará menos trabalho:
df_women_100m['Time'].astype(float)
0 12.20 1 11.90 2 11.50 3 11.90 4 11.50 5 11.50 6 11.00 7 11.40 8 11.00 9 11.07 10 11.08 11 11.06 12 10.97 13 10.54 14 10.82 15 10.94 16 10.93 17 10.78 18 10.75 19 10.71 20 10.61 Name: Time, dtype: float64
Ótimo, o Pandas conseguiu converter sem problemas todos os registros para float. Mas perceba que perdemos a informação de que os dados anteriores a 1972 são de apenas uma casa decimal, pois a conversão coloca todos com duas casas. Por isso é importante uma análise prévia antes de fazer grandes manipulações, pois muitas vezes a análise prévia fornece muitos insights que levam a pesquisas interessantes.
Vamos gravar os dados da coluna em float:
df_women_100m['Time'] = df_women_100m['Time'].astype(float)
E, para facilitar o entendimento, vamos deixar clara a unidade desse tempo, segundos. Isso vai ajudar futuramente também nos gráficos.
df_women_100m = df_women_100m.rename(columns={'Time': 'Time / s'})
Vamos, então, avaliar info
e describe
do nosso dataframe:
df_women_100m.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 21 entries, 0 to 20 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Location 21 non-null object 1 Year 21 non-null int64 2 Name 21 non-null object 3 Nationality 21 non-null object 4 Time / s 21 non-null float64 5 Wind 2 non-null float64 dtypes: float64(2), int64(1), object(3) memory usage: 1.1+ KB
df_women_100m.describe()
Year | Time / s | Wind | |
---|---|---|---|
count | 21.000000 | 21.000000 | 2.000000 |
mean | 1975.809524 | 11.150476 | 1.200000 |
std | 27.927798 | 0.453315 | 2.545584 |
min | 1928.000000 | 10.540000 | -0.600000 |
25% | 1956.000000 | 10.820000 | 0.300000 |
50% | 1976.000000 | 11.000000 | 1.200000 |
75% | 1996.000000 | 11.500000 | 2.100000 |
max | 2020.000000 | 12.200000 | 3.000000 |
df_women_100m.describe(include='object')
Location | Name | Nationality | |
---|---|---|---|
count | 21 | 21 | 21 |
unique | 18 | 17 | 9 |
top | Los Angeles | Gail DEVERS | USA |
freq | 2 | 2 | 9 |
Veja que agora temos Year e Time como colunas numéricas, o que precisamos para nossa apresentação de resultados. No mais, nenhuma atleta ganhou mais de duas vezes o ouro nessa modalidade e o país com mais medalhas de ouro são os Estados Unidos.
Apresentando os resultados. Imagens!
Finalmente, vamos para os gráficos. O Pandas possui uma boa integração com o Matplotlib, sobre o qual já fiz alguns artigos aqui no site. Basta chamar o método plot
e passar quais as colunas dos eixos x e y:
df_women_100m.plot(x='Year', y='Time / s')
<AxesSubplot:xlabel='Year'>
A melhor forma de apresentação seria um gráfico de pontos, já que não muito sentido essas ligações entre os períodos:
df_women_100m.plot.scatter(x='Year', y='Time / s')
<AxesSubplot:xlabel='Year', ylabel='Time / s'>
OK, não é um dos gráficos mais bonitos e muito pode ser melhorado. Já fiz um artigo sobre customização de gráficos com Matplotlib e poderíamos ir por esse lado. No entanto, talvez seja mais prático tentar utilizar estilos prontos:
import matplotlib.pyplot as plt
plt.style.use('ggplot')
df_women_100m.plot.scatter(x='Year', y='Time / s')
<AxesSubplot:xlabel='Year', ylabel='Time / s'>
Melhorou, realmente fica mais apresentável e talvez para um formato de mídia estático, por exemplo impresso, seja adequado com mais alguns ajustes. Mas para uma apresentação em mídia digital, seria interessante mais interatividade. Para isso, vamos substituir o motor gráfico do pandas para o Plotly, um pacote excelente para construção de gráficos interativos:
pd.options.plotting.backend = "plotly"
df_women_100m.plot.scatter(x='Year', y='Time / s')
Já temos uma melhora visual significativa. Mas experimente passar o mouse (ou o dedo, se estiver em algum dispositivo com touch) em cada ponto. Aparece uma informação a respeito dos valores do eixo horizontal e do vertical. Além disso, há uma barra de ferramentas no topo do gráfico, que permite que você faça zoom de regiões e até salve a figura. Excelente para mídias digitais, certo? E ainda podemos melhorar. Vamos enriquecer a informação que aparece em cada ponto, mostrando o nome da atleta e sua nacionalidade:
df_women_100m.plot.scatter(x='Year', y='Time / s',
hover_name='Name',
hover_data=['Nationality'])
Ainda é possível passar mais informações. Podemos brincar com o tamanho de cada ponto e sua coloração. Podemos associar esses dois aspectos ao tempo de cada atleta:
df_women_100m.plot.scatter(x='Year', y='Time / s',
hover_name='Name',
hover_data=['Nationality'],
size='Time / s',
color='Time / s')
Perceba o que o plotly fez. Cada circunferência tem área proporcional ao tempo do atleta. Isso pode ser meio difícil de perceber já que o intervalo de tempos é pequeno, mas olhe o tempo de 1928 e o de 2020 e talvez você perceba. No vídeo explico como poderíamos deixar essa diferença mais evidente. Mas o que chama mais atenção é que o plotly criou automaticamente uma escala de cores que varia gradativamente com o tempo de cada atleta. O nível de informação visual que isso agrega é alto, pois o leitor identifica que se trata de algo gradual mesmo a distância e localiza facilmente os máximos e mínimos.
Mesmo assim, há espaço para melhoras. Usualmente essas escalas são feitas de forma ao valor mais alto ter a cor mais chamativa ou “quente”. No entanto, em nosso contexto, o tempo menor que é mais importante, então seria legal poder mudar esse esquema de cores. Para isso, vamos importar diretamente o plotly. Assim, temos acesso aos temas e esquemas de cores.
Dentre os esquemas de cores, gosto muito do chamado Viridis. Veja que no código abaixo foi utilizado sob o nome Viridis_r
, o _r
é para usar o esquema de cor reverso, de forma que o menor tempo fique com a cor mais atrativa.
import plotly
df_women_100m.plot.scatter(x='Year', y='Time / s',
hover_name='Name',
hover_data=['Nationality'],
size='Time / s',
color='Time / s',
color_continuous_scale=plotly.colors.sequential.Viridis_r,
)
Por fim, vamos dar um título para o gráfico. Sempre deixe o mais explícito possível o que se deseja mostrar com um bom título e com os eixos devidamente identificados.
df_women_100m.plot.scatter(x='Year', y='Time / s',
hover_name='Name',
hover_data=['Nationality'],
size='Time / s',
color='Time / s',
color_continuous_scale=plotly.colors.sequential.Viridis_r,
title="Summer Olympics - Women's 100 m - Gold metalists time progression")
Conclusão
Certamente ainda há muito a se explorar dessa base de dados e também outras melhorias poderiam ser feitas em nosso gráfico. Mas acredito que por esse artigo muito já foi feito e quero manter ele como uma simples introdução ao Pandas. Vou reforçar alguns pontos discutidos:
- obtenção de dados;
- conferência da qualidade, completude e consistência dos dados;
- sempre faça a conferência, mesmo quando os dados são de fontes originais ou obtidos por você, pois erros podem acontecer;
- busque compreender a história e o contexto por trás dos dados;
info
edescribe
são muito úteis;- faça transformações apenas quando elas se mostrarem necessárias;
- pense em qual história se pode contar;
- esteja aberto a novas possibilidades de histórias;
- a ausência de um dado ou um outlier também contam histórias;
- nem só de x e y vive um gráfico, explore tamanhos, formas e cores.
Vou deixar algumas ideias aqui:
- Certamente há alguma forma de indicar que o tempo de 1988 não é o recorde Olímpico. Quem sabe um círculo preto (qualquer cor na real, desde que fora da escala já usada), ou incolor com uma borda mais larga? Ou um outro símbolo que não círculo? E, claro, uma nota de rodapé explicando;
- Igualmente, o tempo de Thanou poderia aparecer como sendo o tempo 2000, mas com alguma distinção dos demais;
- A base de dados é grande, a explore:
-
Será que o tempo entre os medalhistas diminuiu com o tempo? As disputas hoje estão mais próximas que no passado?
-
Quais modalidades estão faltando nessa base de dados?
-
Como a distribuição das medalhas muda com o tempo? Será que algum país que fez parte da União Soviética conseguiu manter a relevância da época da URSS? Como é a progressão de atletas de países subdesenvolvidos ou em desenvolvimento nesses esportes?
-
Seria possível construir um modelo preditivo de tempo para as próximas Olimpíadas?
-
Cada uma dessas ideias pode ser aplicada a cada evento e algumas ao conjunto como um todo. Divirta-se e compartilhe suas explorações nos comentários.
Até a próxima.