read - python pandas limitations




Fluxos de trabalho de “grandes volumes de dados” usando pandas (9)

Como observado por outros, depois de alguns anos um equivalente de pandas 'fora do núcleo' emergiu: dask . Embora a dask não seja um substituto imediato dos pandas e toda a sua funcionalidade, ela se destaca por vários motivos:

O Dask é uma biblioteca de computação paralela flexível para computação analítica otimizada para agendamento de tarefas dinâmicas para cargas de trabalho computacionais interativas de coleções “Big Data”, como matrizes paralelas, quadros de dados e listas que estendem interfaces comuns como iteradores NumPy, Pandas ou Python para de memória ou ambientes distribuídos e escalas de laptops para clusters.

Dask enfatiza as seguintes virtudes:

  • Familiar: Fornece array NumPy paralelizado e objetos DataFrame do Pandas
  • Flexível: fornece uma interface de agendamento de tarefas para mais cargas de trabalho personalizadas e integração com outros projetos.
  • Nativo: permite computação distribuída no Pure Python com acesso à pilha PyData.
  • Rápido: opera com baixa sobrecarga, baixa latência e serialização mínima necessária para algoritmos numéricos rápidos
  • Redimensiona: executa de forma resiliente em clusters com milhares de núcleos Escala reduzida: trivial para configurar e executar em um laptop em um único processo
  • Responsivo: Projetado com a computação interativa em mente, fornece feedback e diagnósticos rápidos para ajudar os humanos

e para adicionar um exemplo de código simples:

import dask.dataframe as dd
df = dd.read_csv('2015-*-*.csv')
df.groupby(df.user_id).value.mean().compute()

substitui alguns códigos de pandas como este:

import pandas as pd
df = pd.read_csv('2015-01-01.csv')
df.groupby(df.user_id).value.mean()

e, especialmente digno de nota, fornece, através da interface concurrent.futures, um general para o envio de tarefas personalizadas:

from dask.distributed import Client
client = Client('scheduler:port')

futures = []
for fn in filenames:
    future = client.submit(load, fn)
    futures.append(future)

summary = client.submit(summarize, futures)
summary.result()

Eu tentei decifrar uma resposta a essa pergunta por muitos meses enquanto aprendia pandas. Eu uso o SAS para o meu dia-a-dia e é ótimo para o suporte fora do núcleo. No entanto, SAS é horrível como um software por inúmeras outras razões.

Um dia, espero substituir meu uso de SAS por python e pandas, mas atualmente não tenho um fluxo de trabalho fora do núcleo para grandes conjuntos de dados. Não estou falando de "big data" que requer uma rede distribuída, mas sim arquivos muito grandes para caber na memória, mas pequenos o suficiente para caber em um disco rígido.

Meu primeiro pensamento é usar o HDFStore para armazenar grandes conjuntos de dados no disco e puxar apenas as peças que eu preciso em quadros de dados para análise. Outros mencionaram o MongoDB como uma alternativa mais fácil de usar. Minha pergunta é esta:

Quais são alguns fluxos de trabalho de melhores práticas para realizar o seguinte:

  1. Carregamento de arquivos simples em uma estrutura de banco de dados permanente em disco
  2. Consultando esse banco de dados para recuperar dados para alimentar em uma estrutura de dados de pandas
  3. Atualizando o banco de dados após manipular peças em pandas

Exemplos do mundo real seriam muito apreciados, especialmente de qualquer pessoa que usa pandas em "grandes dados".

Editar - um exemplo de como eu gostaria que isso funcionasse:

  1. Importe iterativamente um arquivo simples grande e armazene-o em uma estrutura de banco de dados permanente em disco. Esses arquivos geralmente são muito grandes para caber na memória.
  2. Para usar o Pandas, gostaria de ler subconjuntos desses dados (geralmente apenas algumas colunas de cada vez) que podem caber na memória.
  3. Eu criaria novas colunas executando várias operações nas colunas selecionadas.
  4. Eu teria então que acrescentar essas novas colunas na estrutura do banco de dados.

Estou tentando encontrar uma maneira de praticar melhor essas etapas. Lendo links sobre pandas e pytables, parece que acrescentar uma nova coluna pode ser um problema.

Editar - Respondendo às perguntas de Jeff especificamente:

  1. Eu estou construindo modelos de risco de crédito ao consumidor. Os tipos de dados incluem características de telefone, SSN e endereço; valores de propriedade; informações depreciativas, como registros criminais, falências, etc ... Os conjuntos de dados que eu uso todos os dias têm cerca de 1.000 a 2.000 campos, em média, de tipos mistos de dados: variáveis ​​contínuas, nominais e ordinais de dados numéricos e de caracteres. Eu raramente adiciono linhas, mas executo muitas operações que criam novas colunas.
  2. Operações típicas envolvem a combinação de várias colunas usando a lógica condicional em uma nova coluna composta. Por exemplo, if var1 > 2 then newvar = 'A' elif var2 = 4 then newvar = 'B' . O resultado dessas operações é uma nova coluna para cada registro no meu conjunto de dados.
  3. Por fim, gostaria de anexar essas novas colunas à estrutura de dados em disco. Eu repetiria a etapa 2, explorando os dados com tabelas de referência cruzada e estatísticas descritivas, tentando encontrar relações interessantes e intuitivas para modelar.
  4. Um arquivo de projeto típico geralmente é de cerca de 1 GB. Os arquivos são organizados de tal maneira que uma linha consiste em um registro de dados do consumidor. Cada linha tem o mesmo número de colunas para cada registro. Esse sempre será o caso.
  5. É muito raro que eu faça subconjuntos por linhas ao criar uma nova coluna. No entanto, é comum que eu crie subconjuntos nas linhas ao criar relatórios ou gerar estatísticas descritivas. Por exemplo, talvez eu queira criar uma frequência simples para uma linha específica de negócios, por exemplo, cartões de crédito de varejo. Para fazer isso, selecionaria apenas os registros em que a linha de negócios = varejo além das colunas sobre as quais desejo relatar. Ao criar novas colunas, no entanto, eu puxaria todas as linhas de dados e apenas as colunas de que preciso para as operações.
  6. O processo de modelagem requer que eu analise cada coluna, procure relações interessantes com alguma variável de resultado e crie novas colunas compostas que descrevam essas relações. As colunas que eu exploro geralmente são feitas em pequenos conjuntos. Por exemplo, vou me concentrar em um conjunto de 20 colunas que lida apenas com valores de propriedade e observar como elas se relacionam com a inadimplência em um empréstimo. Uma vez que estas são exploradas e novas colunas são criadas, eu então passo para outro grupo de colunas, digamos, ensino universitário, e repito o processo. O que estou fazendo é criar variáveis ​​candidatas que expliquem a relação entre meus dados e algum resultado. No final desse processo, aplico algumas técnicas de aprendizado que criam uma equação dessas colunas compostas.

É raro eu adicionar linhas ao conjunto de dados. Eu quase sempre estarei criando novas colunas (variáveis ​​ou recursos em estatística / linguagem de aprendizado de máquina).


Considere o Ruffus se você seguir o caminho simples de criar um pipeline de dados que é dividido em vários arquivos menores.


Eu acho que as respostas acima estão faltando uma abordagem simples que eu achei muito útil.

Quando eu tenho um arquivo que é muito grande para carregar na memória, eu divido o arquivo em vários arquivos menores (por linha ou cols)

Exemplo: no caso de 30 dias de dados de negociação de ~ 30 GB, eu os divido em um arquivo por dia de ~ 1GB. Eu subsequentemente processo cada arquivo separadamente e agrego os resultados no final

Uma das maiores vantagens é que ele permite o processamento paralelo dos arquivos (vários threads ou processos)

A outra vantagem é que a manipulação de arquivos (como adicionar / remover datas no exemplo) pode ser realizada por comandos normais do shell, o que não é possível em formatos de arquivo mais avançados / complicados.

Esta abordagem não cobre todos os cenários, mas é muito útil em muitos deles


Eu sei que este é um tópico antigo, mas acho que vale a pena conferir a biblioteca do Blaze . Ele é construído para esses tipos de situações.

Dos docs:

O Blaze estende a usabilidade do NumPy e Pandas para a computação distribuída e fora do núcleo. O Blaze fornece uma interface semelhante à do NumPy ND-Array ou Pandas DataFrame, mas mapeia essas interfaces familiares para uma variedade de outros mecanismos computacionais como o Postgres ou o Spark.

Edit: By the way, é apoiado por ContinuumIO e Travis Oliphant, autor de NumPy.


Há agora, dois anos depois da pergunta, um equivalente de pandas 'fora do núcleo': dask . É excelente! Apesar de não suportar toda a funcionalidade do pandas, você pode ir muito longe com isso.


Mais uma variação

Muitas das operações feitas em pandas também podem ser feitas como uma consulta db (sql, mongo)

O uso de um RDBMS ou mongodb permite que você execute algumas das agregações na consulta de banco de dados (que é otimizada para grandes volumes de dados e usa cache e índices de maneira eficiente)

Mais tarde, você pode executar o pós-processamento usando pandas.

A vantagem deste método é que você ganha as otimizações de banco de dados para trabalhar com dados grandes, enquanto ainda define a lógica em uma sintaxe declarativa de alto nível - e não tendo que lidar com os detalhes de decidir o que fazer na memória e o que fazer de núcleo.

E embora a linguagem de consulta e os pandas sejam diferentes, geralmente não é complicado traduzir parte da lógica de um para outro.


Se os seus conjuntos de dados estiverem entre 1 e 20 GB, você deverá obter uma estação de trabalho com 48 GB de RAM. Em seguida, o Pandas pode armazenar todo o conjunto de dados na RAM. Eu sei que não é a resposta que você está procurando aqui, mas fazer computação científica em um notebook com 4GB de RAM não é razoável.


Um truque que achei útil para grandes casos de uso de dados é reduzir o volume dos dados reduzindo a precisão do float para 32 bits. Não é aplicável em todos os casos, mas em muitos aplicativos a precisão de 64 bits é excessiva e a economia de memória de 2x vale a pena. Para tornar um ponto óbvio ainda mais óbvio:

>>> df = pd.DataFrame(np.random.randn(int(1e8), 5))
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float64(5)
memory usage: 3.7 GB

>>> df.astype(np.float32).info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float32(5)
memory usage: 1.9 GB

Vi isso um pouco tarde, mas trabalho com um problema semelhante (modelos de pré-pagamento de hipoteca). Minha solução foi pular a camada de HDFStore dos pandas e usar os pytables diretos. Eu salvo cada coluna como um array HDF5 individual no meu arquivo final.

Meu fluxo de trabalho básico é primeiro obter um arquivo CSV do banco de dados. Eu gzip, então não é tão grande. Então converto isso para um arquivo HDF5 orientado a linhas, iterando-o em python, convertendo cada linha em um tipo de dados real e gravando-o em um arquivo HDF5. Isso leva algumas dezenas de minutos, mas não usa memória alguma, já que está operando linha a linha. Então eu "transponho" o arquivo HDF5 orientado a linha para um arquivo HDF5 orientado a colunas.

A transposição da tabela se parece com:

def transpose_table(h_in, table_path, h_out, group_name="data", group_path="/"):
    # Get a reference to the input data.
    tb = h_in.getNode(table_path)
    # Create the output group to hold the columns.
    grp = h_out.createGroup(group_path, group_name, filters=tables.Filters(complevel=1))
    for col_name in tb.colnames:
        logger.debug("Processing %s", col_name)
        # Get the data.
        col_data = tb.col(col_name)
        # Create the output array.
        arr = h_out.createCArray(grp,
                                 col_name,
                                 tables.Atom.from_dtype(col_data.dtype),
                                 col_data.shape)
        # Store the data.
        arr[:] = col_data
    h_out.flush()

Lendo de volta então parece:

def read_hdf5(hdf5_path, group_path="/data", columns=None):
    """Read a transposed data set from a HDF5 file."""
    if isinstance(hdf5_path, tables.file.File):
        hf = hdf5_path
    else:
        hf = tables.openFile(hdf5_path)

    grp = hf.getNode(group_path)
    if columns is None:
        data = [(child.name, child[:]) for child in grp]
    else:
        data = [(child.name, child[:]) for child in grp if child.name in columns]

    # Convert any float32 columns to float64 for processing.
    for i in range(len(data)):
        name, vec = data[i]
        if vec.dtype == np.float32:
            data[i] = (name, vec.astype(np.float64))

    if not isinstance(hdf5_path, tables.file.File):
        hf.close()
    return pd.DataFrame.from_items(data)

Agora, eu geralmente executo isso em uma máquina com uma tonelada de memória, então eu posso não ser cuidadoso o suficiente com o meu uso de memória. Por exemplo, por padrão, a operação load lê todo o conjunto de dados.

Isso geralmente funciona para mim, mas é um pouco desajeitado, e eu não posso usar a fantasia da magia pytables.

Edit: A vantagem real desta abordagem, sobre o padrão pytables array-of-records, é que eu posso carregar os dados em R usando h5r, que não pode manipular tabelas. Ou, pelo menos, não consegui obtê-lo para carregar tabelas heterogêneas.





large-data