Manipulação de Dados com Python (Pandas)
February 29, 2016
Introdução
Depois de falarmos do básico de leitura de informações no Pandas (neste post), falaremos agora do básico de manipulação de dados com Python, alteração de valores, remoção de colunas, etc. Vamos lá. Vamos novamente usar o Dataset do Titanic para a maior parte de nossos exemplos. Caso não saiba do que se trata, confira o post anterior que já foi citado.
Removendo informações do DataFrame
Vamos começar falando sobre a remoção de informações de um DataFrame. Informações que não sejam úteis em um dado momento sempre podem ser removidas para facilitar seu trabalho.
Para remover informações de um DataFrame, use o drop
. Para o drop
, você deve incluir como argumento o índice a ser retirado, seja ele nome ou posição. Se você não definir o argumento axis
, ele remove a linha. Se você definir axis=1
como argumento, ele remove a coluna. Você também pode passar uma lista como argumento, e todas as linhas ou colunas dentro da lista serão retiradas. É importante notar que ele não remove a informação diretamente do DataFrame em que for usado. Caso você queira manter o que é gerado pelo drop
, deve criar um novo objeto junto com o comando. Vamos remover a coluna “Name” do DataFrame. Ela possui valores muito grandes que deixam a leitura da tabela muito extensa. Vamos manter o DataFrame assim até o fim do post ;)
train_df = pd.read_csv('train.csv')
train_df = train_df.drop('Name',axis=1)
print(train_df.head())
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
0 1 0 3 male 22 1 0 A/5 21171
1 2 1 1 female 38 1 0 PC 17599
2 3 1 3 female 26 0 0 STON/O2. 3101282
3 4 1 1 female 35 1 0 113803
4 5 0 3 male 35 0 0 373450
Fare Cabin Embarked
0 7.2500 NaN S
1 71.2833 C85 C
2 7.9250 NaN S
3 53.1000 C123 S
4 8.0500 NaN S
Agora faremos com as linhas. Repare que os indexes das linhas agora começam com 1, porque removemos a linha 0:
print(train_df.drop(0).head())
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
1 2 1 1 female 38 1 0 PC 17599
2 3 1 3 female 26 0 0 STON/O2. 3101282
3 4 1 1 female 35 1 0 113803
4 5 0 3 male 35 0 0 373450
5 6 0 3 male NaN 0 0 330877
Fare Cabin Embarked
1 71.2833 C85 C
2 7.9250 NaN S
3 53.1000 C123 S
4 8.0500 NaN S
5 8.4583 NaN Q
Alterando valores e criando novas colunas
Para alterar valores de uma coluna, utilizamos a mesma forma de quando estamos extraindo o valor de uma coluna, definindo então o valor. É possível definir um valor único para toda coluna, usar funções como range ou funções para geração de números aleatórios ou então passar uma lista ou array, mas neste caso, a lista ou array deve ter o mesmo comprimento do DataFrame. Vamos utilizar a coluna "Embarked"
para este exemplo mas no fim do exemplo vamos carregar o Dataset de novo pois vamos precisar desta coluna em um exemplo posterior.
train_dataset['Embarked'] = "Porto"
print(train_dataset['Embarked'])
0 Porto
1 Porto
2 Porto
3 Porto
4 Porto
...
886 Porto
887 Porto
888 Porto
889 Porto
890 Porto
train_df = pd.read_csv('train.csv')
train_df = train_df.drop('Name',axis=1)
Você também pode criar uma nova coluna, definindo valores para uma coluna com um nome que ainda não exista no DataFrame. No caso do nosso Dataset, por exemplo, a coluna "SibSp"
é o número de irmãos e cônjuge, e a coluna "Parch"
é o número de filhos ou pais no navio. Vamos criar a coluna "Tamanho_familia"
somando estas duas colunas:
train_df['Tamanho_Familia'] = train_df['SibSp'] + train_df['Parch']
print(train_df['Tamanho_Familia'])
0 1
1 1
2 0
3 1
4 0
...
886 0
887 0
888 3
889 0
890 0
Trabalhando com valores inexistentes (NaN)
Caso o Dataset em que você esteja trabalhando esteja com valores inexistentes demais, o Pandas possui funções para preencher estes valores inexistentes da melhor forma possível.
Primeiramente, você pode verificar onde no seu DataFrame existem valores não existentes, com a função isnull()
. No caso do Dataset do Titanic especificamente, a coluna "Cabin"
possui muitos valores não existentes, então você pode conferir o comando filtrando esta coluna:
train_df['Cabin'].isnull()
0 True
1 False
2 True
3 False
4 True
...
886 True
887 False
888 True
889 False
890 True
Entretanto, se você quer saber quais colunas do seu Dataset possuem valores inexistentes e ele possui muitas linhas e colunas, a abordagem anterior não é muito prática. Para estes casos você pode usar o isnull()
junto com o any()
. Isto lhe dirá, coluna a coluna, se qualquer um dos valores é inexistente. Além disso, você também pode usar os comandos notnull()
e all()
, para dizer se as colunas estão completas:
train_df.isnull().any()
PassengerId False
Survived False
Pclass False
Sex False
Age True
SibSp False
Parch False
Ticket False
Fare False
Cabin True
Embarked True
Tamanho_Familia False
train_df.notnull().all()
PassengerId True
Survived True
Pclass True
Sex True
Age False
SibSp True
Parch True
Ticket True
Fare True
Cabin False
Embarked False
Tamanho_Familia True
Sabendo onde estão seus valores inexistentes, é possível decidir se você quer remover os dados do Dataset, não utilizando estas informações, ou preencher os valores inexistentes com alguma estratégia coerente.
Para começar, o comando dropna()
remove do DataFrame qualquer linha que tenha pelo menos um NaN
. Caso passemos o argumento how='all'
, ele só remove as linhas em que todas as colunas são NaN
. Para remover colunas, usa-se o mesmo comando, mas passando também o argumento axis=1
:
print(train_df.dropna().head())
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
1 2 1 1 female 38 1 0 PC 17599
3 4 1 1 female 35 1 0 113803
6 7 0 1 male 54 0 0 17463
10 11 1 3 female 4 1 1 PP 9549
11 12 1 1 female 58 0 0 113783
Fare Cabin Embarked Tamanho_Familia
1 71.2833 C85 C 1
3 53.1000 C123 S 1
6 51.8625 E46 S 0
10 16.7000 G6 S 2
11 26.5500 C103 S 0
print(train_df.dropna(axis=1).head())
PassengerId Survived Pclass Sex SibSp Parch Ticket \
0 1 0 3 male 1 0 A/5 21171
1 2 1 1 female 1 0 PC 17599
2 3 1 3 female 0 0 STON/O2. 3101282
3 4 1 1 female 1 0 113803
4 5 0 3 male 0 0 373450
Fare Tamanho_Familia
0 7.2500 1
1 71.2833 1
2 7.9250 0
3 53.1000 1
4 8.0500 0
No primeiro caso, repare nos índices das linhas. Muitas linhas foram removidas por possuírem valores inexistentes, possivelmente na coluna "Cabin"
. No segundo caso, a própria coluna "Cabin"
foi removida, juntamente com as colunas "Embarked"
e "Age"
.
Caso você queira preencher os valores inexistentes, você deve usar a função fillna()
. Nela, você passa o valor que deve substituir os NaN
. Neste caso, provavelmente é melhor proceder coluna a coluna, devido a fatores como diferença no tipo de dados e também uma estratégia diferente na hora de preencher os valores inexistentes. Como vimos mais acima, as colunas "Age"
e "Embarked"
possuem valores inexistentes. Vamos preencher a coluna "Age"
com a média das idades que temos no Dataset e a coluna "Embarked"
com a moda, ou seja, o valor que mais ocorre nesta coluna. O fillna()
, por padrão, retorna um novo objeto, mas você pode utilizar o argumento inplace=True
para modificar o objeto original. Vamos então criar um novo DataFrame, filled_df
, apenas para preservar o original, e vamos então preencher os inexistentes. Vejamos:
filled_df = train_df
filled_df['Age'].fillna(filled_df['Age'].mean(), inplace=True)
filled_df['Embarked'].fillna(filled_df['Embarked'].mode()[0], inplace=True)
# Agora podemos ver que as colunas Age e Embarked não possuem mais valores inexistentes
print(filled_df.isnull().any())
PassengerId False
Survived False
Pclass False
Sex False
Age False
SibSp False
Parch False
Ticket False
Fare False
Cabin True
Embarked False
Tamanho_Familia False
Removendo duplicados
Para trabalhar com linhas duplicadas, temos duas funções. A primeira, duplicated
indica se cada linha é duplicada ou não. Por padrão, só retorna True
se a linha inteira é exatamente igual a alguma anterior. Para verificar a repetição baseado em uma coluna, podemos passar o nome desta coluna como argumento, dentro de uma lista. Dentro desta lista também podemos passar mais de um nome de coluna, e neste caso será indicado True
se o valor de todas estas colunas for igual. Já a função drop_duplicates
já remove todas as colunas duplicadas. Funciona da mesma forma que a anterior, ou seja, por padrão só remove a linha se ela for inteira igual a alguma linha anterior, mas pode remover baseando-se em valores de colunas. Vamos ver alguns exemplos com a coluna de "Embarked"
. Por ter somente três valores, sabemos que vamos ver muitos valores duplicados nela:
print(train_df.duplicated("Embarked"))
0 False
1 False
2 True
3 True
4 True
5 False
...
885 True
886 True
887 True
888 True
889 True
890 True
drop_duplicate_df = train_df.drop_duplicates(['Embarked'])
print(drop_duplicate_df)
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
0 1 0 3 male 22.000000 1 0 A/5 21171
1 2 1 1 female 38.000000 1 0 PC 17599
5 6 0 3 male 29.699118 0 0 330877
Fare Cabin Embarked Tamanho_Familia
0 7.2500 NaN S 1
1 71.2833 C85 C 1
5 8.4583 NaN Q 0
Algumas Estatísticas em DataFrames
O Pandas também possui uma série de comandos para cálculo de estatísticas descritivas ao longo de um DataFrame, podendo calcular ao longo de linhas ou colunas. Entre estas estatísticas estão média, moda, mediana, contagem, soma, entre muitos outros. Por padrão, o cálculo é feito por coluna, mas caso você passe o argumento axis=1
, o cálculo é feito ao longo das linhas. Vamos mostrar alguns exemplos e depois uma tabela com todas as funções, de acordo com a documentação oficial:
train_df.sum()
PassengerId 397386.0000
Survived 342.0000
Pclass 2057.0000
Age 21205.1700
SibSp 466.0000
Parch 340.0000
Fare 28693.9493
Tamanho_Familia 806.0000
train_df.mean()
PassengerId 446.000000
Survived 0.383838
Pclass 2.308642
Age 29.699118
SibSp 0.523008
Parch 0.381594
Fare 32.204208
Tamanho_Familia 0.904602
train_df.max(axis=1)
0 22.0000
1 71.2833
2 26.0000
3 53.1000
4 35.0000
...
886 887.0000
887 888.0000
888 889.0000
889 890.0000
890 891.0000
A lista completa de funções pode ser encontrada neste link aqui, da documentação oficial: http://pandas.pydata.org/pandas-docs/stable/basics.html#descriptive-statistics
Ordenando DataFrames (sorting)
Para leitura dos dados, também pode ser importante ordenar um DataFrame, ou seja, fazer um Sort
. Para ordenar pelos índices das linhas, vamos primeiro reindexar o DataFrame para que os índices fiquem revertidos. Depois vamos usar a função sort_index()
para ordena-lo. Você também pode passar o argumento ascending=False
para ordena-lo de forma descendente:
# Aqui invertemos a ordem do DataFrame
new_train_df = train_df.reindex(list(reversed(range(891))))
new_train_df.head()
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
890 891 0 3 male 32 0 0 370376
889 890 1 1 male 26 0 0 111369
888 889 0 3 female NaN 1 2 W./C. 6607
887 888 1 1 female 19 0 0 112053
886 887 0 2 male 27 0 0 211536
Fare Cabin Embarked Tamanho_Familia
890 7.75 NaN Q 0
889 30.00 C148 C 0
888 23.45 NaN S 3
887 30.00 B42 S 0
886 13.00 NaN S 0
# Aqui, reordenamos na ordem certa através do sort_index()
new_train_df = new_train_df.sort_index()
new_train_df.head()
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
0 1 0 3 male 22 1 0 A/5 21171
1 2 1 1 female 38 1 0 PC 17599
2 3 1 3 female 26 0 0 STON/O2. 3101282
3 4 1 1 female 35 1 0 113803
4 5 0 3 male 35 0 0 373450
Fare Cabin Embarked Tamanho_Familia
0 7.2500 NaN S 1
1 71.2833 C85 C 1
2 7.9250 NaN S 0
3 53.1000 C123 S 1
4 8.0500 NaN S 0
# Aqui você pode usar sort_index para ordenar de forma descendente
new_train_df.sort_index(ascending=False).head()
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
890 891 0 3 male 32 0 0 370376
889 890 1 1 male 26 0 0 111369
888 889 0 3 female NaN 1 2 W./C. 6607
887 888 1 1 female 19 0 0 112053
886 887 0 2 male 27 0 0 211536
Fare Cabin Embarked Tamanho_Familia
890 7.75 NaN Q 0
889 30.00 C148 C 0
888 23.45 NaN S 3
887 30.00 B42 S 0
886 13.00 NaN S 0
Também é possível ordenar pelos valores de uma coluna, através da função sort_values()
. Dentro dela, você especifica através do argumento by
o nome da coluna através da qual o DataFrame será ordenado:
new_train_df.sort_values(by='Age').head()
PassengerId Survived Pclass Sex Age SibSp Parch Ticket \
803 804 1 3 male 0.42 0 1 2625
755 756 1 2 male 0.67 1 1 250649
644 645 1 3 female 0.75 2 1 2666
469 470 1 3 female 0.75 2 1 2666
78 79 1 2 male 0.83 0 2 248738
Fare Cabin Embarked Tamanho_Familia
803 8.5167 NaN C 1
755 14.5000 NaN S 2
644 19.2583 NaN C 3
469 19.2583 NaN C 3
78 29.0000 NaN S 2
E com isso, encerramos mais um post falando um pouco mais sobre as funcionalidades principais do Pandas. Ainda estou cogitando sobre o próximo post, mas possívelmente falará sobre visualização de dados, matplotlib e como fazer alguns gráficos e visualizações.
Fiquem ligados! :)