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! :)