[Data Science] Lendo dados do jeito certo com Pandas

Edilson Silva
5 min readJul 10, 2023

--

Trabalhar com Data Science não é uma tarefa nada fácil por envolver diversos conceitos e técnicas que levam um tempo para serem assimiladas completamente.

Um dos grandes problemas está na manipulação e tratativa de dados, pois, a medida em que o tempo passa, eles tornam-se crescentes e mais frequentes, levando-nos a uma caçada incessante por novas formas de desempenhar tais atividades.

O principal ponto que devemos considerar quando falamos de manipulação de dados é o uso excessivo de recursos computacionais, uma vez que, em produção não os teremos em abundância tal qual temos localmente.

Uma das melhores formas de trabalhar com grandes massas de dados é utilizando a biblioteca Pandas, que nos dá uma interface simples para manipulá-los, mas, mesmo assim, é necessário um olhar mais profundo e especialista para não cairmos em armadilhas.

Utilizaremos como exemplo o DataSet com dados de passageiros do Titanic:

O DataSet em questão é pequeno, mas servirá perfeitamente para demostrar alguns truques que temos disponíveis e que otimizarão nossa carga de dados.

Conhecer bem os nossos dados é um pré-requisito para sabermos exatamente onde e quando otimizar, então, daremos uma olhada em um pequeno exemplo dos dados de nosso DataSet.

Exemplo de dados do DataSet Titanic.
Exemplo de dados do DataSet Titanic.

Função utilitária para extrair uso de memória de DataFrame:

import io
import re

def get_pandas_dataframe_info(df):
buf = io.StringIO()
df.info(buf=buf)

info = buf.getvalue()

result = re.search(r'.*memory usage:\s(.*)', info)
return result.group(1)

1 — Carregamento padrão (Sem otimizações)

Ao executarmos o código abaixo, estaremos carregando o DataSet inteiro para a memória.

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'

df = pd.read_csv(data_source)
rows, cols = df.shape

print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

Rows: 891
Cols: 12
Memory Size: 83.7+ KB

2 — Limitar quantidade de linhas

Limitar a quantidade de linhas a serem carregadas é uma forma simples e útil de economizar memória, pois estaremos trabalhando sob demanda.

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
nrows = 100

df = pd.read_csv(data_source, nrows=nrows)
rows, cols = df.shape

print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

Rows: 100
Cols: 12
Memory Size: 9.5+ KB

3 — Selecionar colunas desejadas

Limitar as colunas a serem carregadas pode ser uma forma inteligente de trabalhar, pois, como nos deparamos com situações adversas em dados, podemos ter colunas totalmente inúteis e que consumiriam espaço desnecessário.

3.1 — Seleção por nomes

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
usecols = [
'PassengerId',
'Survived',
'Pclass',
'Sex',
'Age',
'Embarked'
]

df = pd.read_csv(data_source, usecols=usecols)
rows, cols = df.shape

print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

3.2 — Seleção por índices

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
usecols = [0, 1, 2, 4, 5, 7]

df = pd.read_csv(data_source, usecols=usecols)
rows, cols = df.shape

print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

Ambos os códigos acima terão o mesmo efeito e consumirão a mesma quantidade de memória, pois a única diferença entre eles é que ao invés de indexarmos as colunas por nome, utilizaremos seus índices numéricos.

Rows: 891
Cols: 6
Memory Size: 41.9+ KB

4 — Definindo tipos dos dados

Uma das formas mais inteligentes de se trabalhar com Pandas é realizar alterações nos tipos de dados.

Por padrão o Pandas executa uma análise em cada série de dados (coluna) e então infere o tipo a ser atribuído para ela, porém, nem sempre esta inferência é assertiva.

4.1 — Definição manual

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'

df = pd.read_csv(data_source)
rows, cols = df.shape

Verificando os tipos inferidos:

print(df.dtypes)
PassengerId      int64
Survived int64
Pclass int64
Name object
Sex object
Age float64
SibSp int64
Parch int64
Ticket object
Fare float64
Cabin object
Embarked object

Ao visualizarmos os tipos atribuídos, podemos perceber que houve uma série de "erros" de inferência e que majoritariamente os dados numéricos foram definidos como int64 ,enquanto os não numéricos foram definidos como object.

Após uma inspeção nos dados é possível constatar que há otimizações válidas a serem feitos, através da definição de algumas colunas numéricas para int32 e outras para category (dados categóricos).

Ajustando os tipos:

df.Sex = df.Sex.astype('category')
df.Embarked = df.Embarked.astype('category')
df.Survived = df.Survived.astype('category')
df.Pclass = df.Pclass.astype('category')
df.PassengerId = df.PassengerId.astype('int32')
df.Parch = df.Parch.astype('int32')
df.SibSp = df.SibSp.astype('int32')

Verificando os tipos ajustados:

print(df.dtypes)
PassengerId       int32
Survived category
Pclass category
Name object
Sex category
Age float64
SibSp int32
Parch int32
Ticket object
Fare float64
Cabin object
Embarked category
print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

Rows: 891
Cols: 12
Memory Size Original: 49.4+ KB.

Após as alterações podemos constatar que o consumo foi de 83.7+ KB para 49.4+ KB, denotando uma redução de cerca de 41%.

4.2 — Definição em tempo de carregamento

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
dtypes = {
'Sex': 'category',
'Embarked': 'category',
'Survived': 'category',
'Pclass': 'category',
'PassengerId': 'int32',
'Parch': 'int32',
'SibSp': 'int32'
}

df = pd.read_csv(data_source)
rows, cols = df.shape

Verificando os tipos definidos:

print(df.dtypes)
PassengerId       int32
Survived category
Pclass category
Name object
Sex category
Age float64
SibSp int32
Parch int32
Ticket object
Fare float64
Cabin object
Embarked category

Ao definir os dados em tempo de leitura, o Pandas carregará os dados já com os tipos definidos.

É preciso ter cuidado para não definir tipos incorretos, pois, isso pode levar a perda de dados, tal qual pode ocorrer com valores numéricos arredondados ou cortados.

print(f'Rows: {rows}')
print(f'Cols: {cols}')
print(f'Memory Size: {get_pandas_dataframe_info(df)}')

Rows: 891
Cols: 12
Memory Size: 49.2+ KB

Ao informarmos os tipos de dados corretos no momento de leitra, obtivemos um resultado ligeiramente melhor que na conversão manual, indo de 83.7+ KB para 49.2+ KB, denotando uma redução em cerca de 41%.

5 — [Bonus] Carregamento sob demanda (Chunks)

Trabalhar com o carregamento em blocos, traz a vantagem de termos em memória somente o necessário para o trabalho atual, sendo este descartado ao final do processo.

import pandas as pd

data_source = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'

df = pd.read_csv(data_source, chunksize=200)

print(f'{"Chunk":^10}|{"Memory Size":^13}|{"Rows":^10}|{"Cols":^8}')

for i, chunk in enumerate(df):
chunk_memory_info = get_pandas_dataframe_info(chunk)
chunk_rows, chunk_cols = chunk.shape
print(f'{(i+1):^10}|{chunk_memory_info:^13}|{chunk_rows:^10}|{chunk_cols:^8}')
  Chunk   | Memory Size |   Rows   |  Cols  
1 | 18.9+ KB | 200 | 12
2 | 18.9+ KB | 200 | 12
3 | 18.9+ KB | 200 | 12
4 | 18.9+ KB | 200 | 12
5 | 8.7+ KB | 91 | 12

Com o exemplo anterior é possível ver que os dados foram particionados em blocos, tendo estes no máximo 200 linhas e consumindo o máximo de 18.9+ KB.

Como dito lá no começo, trabalhar com dados não é nada fácil, mas com as dicas acima tenho certeza que será bem menos difícil.

Repositório de códigos: https://github.com/edilson-silva/datadevtips/blob/main/data-science/tools/pandas/pandas_reading_large_data.ipynb

Instagram do projeto DataDevTips: https://www.instagram.com/datadevtips/

--

--