Pandas: Nenhuma maneira de construir dtype DataFrame misto sem cópia total, solução proposta

Criado em 9 jan. 2015  ·  58Comentários  ·  Fonte: pandas-dev/pandas

Depois de horas arrancando meus cabelos, cheguei à conclusão de que é impossível criar um DataFrame de dtipo misto sem copiar todos os seus dados. Ou seja, não importa o que você faça, se quiser criar um DataFrame de dtipo misto , você inevitavelmente criará uma versão temporária dos dados (por exemplo, usando np.empty), e os vários construtores de DataFrame sempre farão cópias deste temporário. Este problema já foi levantado, há um ano: https://github.com/pydata/pandas/issues/5902.

Isso é especialmente terrível para a interoperabilidade com outras linguagens de programação. Se você planeja preencher os dados no DataFrame de, por exemplo, uma chamada para C, a maneira mais fácil de fazer isso é criar o DataFrame em python, obter ponteiros para os dados subjacentes, que são np.arrays, e passar esses np .arranjos ao longo de modo que possam ser preenchidos. Nessa situação, você simplesmente não se importa com os dados com os quais o DataFrame começa, o objetivo é apenas alocar a memória para que você saiba para onde está copiando.

Isso também é geralmente frustrante porque implica que, em princípio (dependendo potencialmente da situação específica e das especificações de implementação, etc.), é difícil garantir que você não acabará usando o dobro da memória que realmente deveria.

Isso tem uma solução extremamente simples que já está baseada na pilha quantitativa do python: tenha um método análogo ao numpy's empty. Isso aloca o espaço, mas na verdade não perde tempo escrevendo ou copiando nada. Uma vez que empty já foi usado, eu proporia chamar o método from_empty. Aceitaria um índice (obrigatório, o caso de uso mais comum seria passar np.arange (N)), colunas (obrigatório, normalmente uma lista de strings), tipos (lista de tipos aceitáveis ​​para colunas, mesmo comprimento das colunas). A lista de tipos deve incluir suporte para todos os tipos numéricos numpy (ints, floats), bem como colunas Pandas especiais, como DatetimeIndex e Categorical.

Como um bônus adicional, uma vez que a implementação está em um método completamente separado, ela não irá interferir com a API existente.

API Design Constructors Dtypes

Comentários muito úteis

Existem muitos tópicos no SO solicitando esse recurso.

Parece-me que todos esses problemas decorrem do BlockManager consolidando colunas separadas em um único bloco de memória (os 'blocos').
A correção mais fácil não seria não consolidar dados em blocos quando copy = False for especificado.

Eu tenho um BlockManager corrigido por macaco que não consolida:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
que usei para contornar esse problema.

Todos 58 comentários

você pode simplesmente criar um quadro vazio com um índice e colunas
então atribua ndarrays - estes não copiarão de você atribuir todos de um dtype particular de uma vez

você pode criá-los com np.empty se desejar

df = pd.DataFrame(index=range(2), columns=["dude", "wheres"])

df
Out[12]:
  dude wheres
0  NaN    NaN
1  NaN    NaN

x = np.empty(2, np.int32)

x
Out[14]: array([6, 0], dtype=int32)

df.dude = x

df
Out[16]:
   dude wheres
0     6    NaN
1     0    NaN

x[0] = 0

x
Out[18]: array([0, 0], dtype=int32)

df
Out[19]:
   dude wheres
0     6    NaN
1     0    NaN

Parece que está copiando para mim. A menos que o código que escrevi não seja o que você quis dizer, ou a cópia ocorrida não seja a cópia que você pensava que eu estava tentando omitir.

você mudou o tipo d
é por isso que copiou tente com um flutuador

y = np.empty(2, np.float64)

df
Out[21]:
   dude wheres
0     6    NaN
1     0    NaN

df.wheres = y

y
Out[23]: array([  2.96439388e-323,   2.96439388e-323])

y[0] = 0

df
Out[25]:
   dude         wheres
0     6  2.964394e-323
1     0  2.964394e-323

df = pd.DataFrame(index=range(2), columns=["dude", "wheres"])

df.dtypes
Out[27]:
dude      object
wheres    object
dtype: object

O dtype é objeto, então ele é alterado independentemente de eu usar um float ou um int.

In [25]: arr = np.ones((2,3))

In [26]: df = DataFrame(arr,columns=['a','b','c'])

In [27]: arr[0,1] = 5

In [28]: df
Out[28]: 
   a  b  c
0  1  5  1
1  1  1  1

Construir sem uma cópia em um tipo misto pode ser feito, mas é bastante complicado. O problema é que alguns tipos exigem uma cópia (por exemplo, objeto para evitar problemas de contenção de memória). E a estrutura interna consolida diferentes tipos, portanto, adicionar um novo tipo exigirá uma cópia. Evitar uma cópia é muito difícil na maioria dos casos.

Você deve apenas criar o que precisa, obter ponteiros para os dados e sobrescrevê-los. Por que isso é um problema?

O problema é que para criar o que preciso, tenho que copiar em material do tipo correto, cujos dados não pretendo utilizar. Mesmo supondo que sua sugestão de criar um DataFrame vazio não use RAM significativa, isso não alivia o custo de cópia. Se eu quiser criar um DataFrame de 1 gigabyte e preenchê-lo em outro lugar, terei que pagar o custo de copiar um gigabyte de lixo na memória, o que é totalmente desnecessário. Você não vê isso como um problema?

Sim, eu entendo que a estrutura interna consolida diferentes tipos. Não sei exatamente o que você quer dizer com problemas de contenção de memória, mas, em qualquer caso, os objetos não são realmente o que interessa aqui.

Na verdade, embora evitar cópias em geral seja um problema difícil, evitá-las da maneira que sugeri é bastante fácil, porque estou fornecendo todas as informações necessárias desde o início. É idêntico a construir a partir de dados, exceto que, em vez de inferir os dtypes e o número de linhas dos dados e copiar os dados, você especifica os dtypes e o número de linhas diretamente e faz todo o resto exatamente como faria sem a cópia.

Você precisa de um construtor "vazio" para cada tipo de coluna compatível. Para tipos numéricos numpy, isso é óbvio, ele precisa de trabalho diferente de zero para Categórico, sem certeza sobre DatetimeIndex.

passar um dict para o construtor e copy = False deve funcionar

Então isso vai funcionar. Mas você tem que ter CERTEZA de que os arrays que você está passando são dtypes distintos. E uma vez que você faça qualquer coisa para isso, ele pode copiar os dados subjacentes. Então YMMV. você pode, é claro, passar np.empty vez dos uns / zeros que eu sou.

In [75]: arr = np.ones((2,3))

In [76]: arr2 = np.zeros((2,2),dtype='int32')

In [77]: df = DataFrame(arr,columns=list('abc'))

In [78]: df2 = DataFrame(arr2,columns=list('de'))

In [79]: result = pd.concat([df,df2],axis=1,copy=False)

In [80]: arr2[0,1] = 20

In [81]: arr[0,1] = 10

In [82]: result
Out[82]: 
   a   b  c  d   e
0  1  10  1  0  20
1  1   1  1  0   0

In [83]: result._data
Out[83]: 
BlockManager
Items: Index([u'a', u'b', u'c', u'd', u'e'], dtype='object')
Axis 1: Int64Index([0, 1], dtype='int64')
FloatBlock: slice(0, 3, 1), 3 x 2, dtype: float64
IntBlock: slice(3, 5, 1), 2 x 2, dtype: int32

In [84]: result._data.blocks[0].values.base
Out[84]: 
array([[  1.,  10.,   1.],
       [  1.,   1.,   1.]])

In [85]: result._data.blocks[1].values.base
Out[85]: 
array([[ 0, 20],
       [ 0,  0]], dtype=int32)

_ Tentativa inicial excluída porque não funciona, pois reindex força o lançamento, que é um "recurso" estranho ._

Tem que usar 'método', o que torna esta tentativa um pouco menos satisfatória:

arr = np.empty(1, dtype=[('x', np.float), ('y', np.int)])
df = pd.DataFrame.from_records(arr).reindex(np.arange(100))

Se você está realmente preocupado com o desempenho, não sei por que não usar o numpy o máximo possível, já que é conceitualmente muito mais simples.

jreback, obrigado por sua solução. Isso parece funcionar, mesmo para Categóricos (o que me surpreendeu). Se eu encontrar problemas, avisarei você. Não tenho certeza do que você quer dizer com: se você fizer alguma coisa com isso, ele pode copiar. O que você quer dizer com qualquer coisa? A menos que haja semântica COW, eu acho que o que você vê é o que você obtém em relação às cópias profundas versus rasas, no momento da construção.

Ainda acho que um construtor from_empty deve ser implementado e não acho que seria tão difícil, embora essa técnica funcione, ela envolve muito código extra. Em princípio, isso poderia ser feito especificando um único dtipo composto e várias linhas.

bashtage, essas soluções ainda gravam em todo o DataFrame. Como a escrita é geralmente mais lenta do que a leitura, isso significa que, na melhor das hipóteses, economiza-se menos da metade do overhead em questão.

Obviamente, se eu não usei o numpy, é porque o pandas tem muitos recursos e capacidades incríveis que eu adoro e não quero desistir deles. Você estava realmente perguntando ou apenas insinuando que eu deveria usar numpy se não quisesse levar essa perda de desempenho?

Raspe isso, por favor, erro do usuário e minhas desculpas. reindex_axis com copy = False funcionou perfeitamente.

bashtage, essas soluções ainda gravam em todo o DataFrame. Como a escrita é geralmente mais lenta do que a leitura, isso significa que, na melhor das hipóteses, economiza-se menos da metade do overhead em questão.

Verdade, mas tudo o que você precisa é um novo method para reindex que não será preenchido com nada e então você pode alocar uma matriz digitada com tipos de coluna arbitrários sem escrever / copiar.

Obviamente, se eu não usei o numpy, é porque o pandas tem muitos recursos e capacidades incríveis que eu adoro e não quero desistir deles. Você estava realmente perguntando ou apenas insinuando que eu deveria usar numpy se não quisesse levar essa perda de desempenho?

Foi um pouco retórico - embora também uma sugestão séria do ponto de vista do desempenho, já que o numpy torna muito mais fácil chegar perto do acesso aos dados como um blob da memória que é importante se você está tentando escrever muito código de alto desempenho. Você sempre pode converter de numpy para pandas quando a simplicidade do código é mais importante do que o desempenho.

Eu vejo o que você está dizendo. Ainda acho que deveria ser parte da interface mais limpa do que uma solução alternativa, mas como soluções alternativas, é uma boa e fácil de implementar.

O Pandas ainda enfatiza o desempenho como um de seus principais objetivos. Obviamente, ele possui recursos de nível superior em comparação com o numpy, e eles devem ser pagos. O que estamos falando não tem nada a ver com esses recursos de nível superior, e não há razão para que alguém deva pagar por cópias massivas em lugares onde você não precisa delas. Sua sugestão seria apropriada se alguém estivesse reclamando do custo de configuração das colunas, índice, etc, o que é completamente diferente desta discussão.

Acho que você está superestimando o custo de escrita em comparação com o código de alocação de memória em Python - a parte cara é a alocação de memória. A criação de objetos também é cara.

Ambos alocam 1 GB de memória, um vazio e um zeros.

%timeit np.empty(1, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 2.44 µs per loop

%timeit np.zeros(1, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 2.47 µs per loop

%timeit np.zeros(50000000, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 11.7 µs per loop

%timeit np.empty(50000000, dtype=[('x', float), ('y', int), ('z', float)])
100000 loops, best of 3: 11.4 µs per loop

3 µs para zerar 150.000.000 de valores.

Agora compare-os com um DataFrame trivial.

%timeit pd.DataFrame([[0]])
1000 loops, best of 3: 426 µs per loop

Cerca de 200 vezes mais lento para trivial. Mas é muito pior para matrizes maiores.

%timeit pd.DataFrame(np.empty((50000000, 3)),copy=False)
1 loops, best of 3: 275 ms per loop

Agora leva 275 ms - observe que isso não está copiando nada. O custo está na configuração do índice, etc., que é claramente muito lento quando a matriz não é trivialmente grande.

Isso parece uma otimização prematura para mim, já que os outros overheads nos pandas são tão grandes que o componente malloc + filliing está perto do custo zero.

Parece que se você deseja alocar qualquer coisa em um loop fechado, deve ser uma matriz numpy por motivos de desempenho.

ok, aqui está o que acho que devemos fazer, @quicknir se você quiser fazer algumas melhorias. 2 questões.

  • # 4464 - isso é essencialmente permitindo um dtype composto no construtor DataFrame e, em seguida, virar e chamar from_records() , que também pode ser chamado se o array passado for um array rec / estruturado - isso basicamente tornaria from_records o caminho de processamento de array rec / estruturado
  • passar pela palavra-chave copy= para from_records
  • from_records pode então usar o concat soln que mostro acima, em vez de dividir o rec-array, higienizando-os (como série) e, em seguida, juntando-os novamente (em blocos dtype; esta parte é feito internamente).

Isso é um pouco não trivial, mas permitiria a passagem em um ndarray já criado (pode estar vazio) com tipos mistos com bastante facilidade. Observe que isso provavelmente (em uma implementação de primeira passagem) manipularia apenas (int / float / string). já que datetime / timedelta precisa de uma higienização especial e tornaria isso um pouco mais complicado.

então @bashtage está certo do ponto de vista do desempenho. Faz muito sentido simplesmente construir o quadro como você deseja e, em seguida, modificar os ndarrays (mas você DEVE fazer isso agarrando os blocos, caso contrário, obterá cópias).

O que eu quis dizer acima é isso. O Pandas agrupa qualquer tipo semelhante (por exemplo, int64, int32 são diferentes) em um 'bloco' (2-d em um quadro). Esses são um ndarray de memória contígua (que é recentemente alocado, a menos que seja simplesmente passado, o que atualmente funciona para um único dtype). Se você então fizer um setitem, por exemplo, df['new_columns'] = 5 e você já tiver um bloco int64, então esta nova coluna será finalmente concatetada a ele (resultando em uma nova alocação de memória para aquele dtype). Se você estava usando uma referência como uma visão sobre isso, ela não será mais válida. É por isso que essa não é uma estratégia que você possa empregar sem peering nas partes internas do DataFrame.

@bashtage yeh o grande custo é o índice, como você notou. um RangeIndex (veja # 939) resolveria este problema completamente. (na verdade, é quase feito em um galho lateral, só precisa de um pouco de poeira).

Mesmo com um RangeIndex otimizado, ainda será 2 ordens de magnitude mais lento do que construir uma matriz NumPy, o que é justo, dada a natureza muito mais pesada e as capacidades adicionais de um DataFrame .

Acho que isso só pode ser considerado uma função de conveniência, e não um problema de desempenho. Pode ser útil inicializar um tipo misto DataFrame ou Panel como.

dtype=np.dtype([('GDP', np.float64), ('Population', np.int64)])
pd.Panel(items=['AU','AT'],
         major_axis=['1972','1973'],
         minor_axis=['GDP','Population'], 
         dtype=[np.float, np.int64])

este é apenas um problema de API / conveniência

concordou que o desempenho é realmente um problema incidental (e não o driver)

@bashtage

% timeit pd.DataFrame (np.empty ((100, 1000000)))
100 loops, melhor de 3: 15,6 ms por loop

% timeit pd.DataFrame (np.empty ((100, 1000000)), copiar = Verdadeiro)
1 loops, melhor de 3: 302 ms por loop

Portanto, copiar para um dataframe parece demorar 20 vezes mais do que todo o trabalho envolvido na criação do DataFrame, ou seja, a cópia (e alocação extra) é 95% do tempo. Os benchmarks que você fez não compararam a coisa correta. Não importa se a cópia em si ou a alocação está levando tempo, realmente não importa, o ponto é que se eu pudesse evitar cópias para um DataFrame de vários tipos d da maneira que faço para um único DataFrame de tipos d, eu poderia economizar uma grande quantidade de tempo.

Seu raciocínio de duas ordens de magnitude também engana. Esta não é a única operação executada; há outras operações sendo executadas que demoram, como a leitura do disco. No momento, a cópia extra que preciso fazer para criar o DataFrame está levando cerca de metade do tempo em meu programa simples, que apenas lê os dados do disco para um DataFrame. Se demorasse 1/20 do tempo, a leitura do disco seria dominante (como deveria ser) e outras melhorias quase não teriam efeito.

Portanto, quero enfatizar novamente para vocês dois: este é um problema real de desempenho.

jreback, dado que a estratégia de concatenação não funciona para categóricos, não pense que as melhorias sugeridas acima funcionarão. Acho que um ponto de partida melhor seria reindexar. O problema agora é que a reindexação faz muitas coisas extras. Mas, a princípio, um DataFrame com zero linhas possui todas as informações necessárias para permitir a criação de um DataFrame com o número correto de linhas, sem fazer nenhum trabalho desnecessário. A propósito, isso realmente me faz sentir que os pandas precisam de um objeto de esquema, mas isso é uma discussão para outro dia.

Acho que teremos que concordar em discordar. Os DataFrames da IMO não são objetos de desempenho extremo no ecossistema numérico, como mostra a ordem de diferença de magnitude entre um array numpy básico e a criação de um DataFrame.

%timeit np.empty((1000000, 100))
1000 loops, best of 3: 1.61 ms per loop

%timeit pd.DataFrame(np.empty((1000000,100)))
100 loops, best of 3: 15.3 ms per loop

No momento, a cópia extra que preciso fazer para criar o DataFrame está levando cerca de metade do tempo em meu programa simples, que apenas lê os dados do disco para um DataFrame. Se demorasse 1/20 do tempo, a leitura do disco seria dominante (como deveria ser) e outras melhorias quase não teriam efeito.

Acho que isso é ainda menos motivo para se preocupar com o desempenho do DataFrame - mesmo que você possa torná-lo 100% gratuito, o tempo total do programa diminui apenas 50%.

Concordo que há espaço para você fazer uma RP aqui para resolver esse problema, quer você queira pensar nisso como um problema de desempenho ou de conveniência. Do meu ponto de vista, vejo como o último, pois sempre usarei uma matriz numpy quando me importo com o desempenho. O Numpy faz outras coisas como não usar um gerenciador de blocos que é relativamente eficiente para algumas coisas (como aumentar o array adicionando colunas). mas ruim de outros pontos de vista.

Pode haver duas opções. O primeiro, um construtor vazio como no exemplo que dei acima. Isso não copiaria nada, mas provavelmente preencheria nulo para ser consistente com outras coisas em pandas. O recheio nulo é muito barato e não está na raiz do problema IMO.

A outra seria ter um método DataFrame.from_blocks que faria com que os blocos pré-formados passassem direto para o gerenciador de blocos. Algo como

DataFrame.from_blocks([np.empty((100,2)), 
                       np.empty((100,3), dtype=np.float32), 
                       np.empty((100,1), dtype=np.int8)],
                     columns=['f8_0','f8_1','f4_0','f4_1','f4_2','i1_0'],
                     index=np.arange(100))

Um método desse tipo faria com que os blocos tivessem forma compatível, todos os blocos tivessem tipos exclusivos, bem como as verificações usuais para a forma do índice e das colunas. Este tipo de método não faria nada aos dados e o usaria no BlockManger.

@quicknir você está tentando combinar coisas muito complicadas. Categóricos não existem em numpy, em vez disso, eles são um tipo composto, como se fosse uma construção de pandas. Você tem que construir e atribuir separadamente (o que é realmente muito barato - eles não são combinados em blocos como outros dtypes singulares).

@bashtage soln parece razoável. Isso pode fornecer algumas verificações simples e simplesmente passar os dados (e ser chamado por outras rotinas internas). Normalmente, o usuário não precisa se preocupar com a repr. Interna. Já que você realmente quer, então você precisa estar ciente disso.

Dito isso, ainda não sei por que você simplesmente não cria uma moldura exatamente como deseja. Em seguida, pegue os ponteiros do bloco e altere os valores. Custa a mesma memória e, como @bashtage aponta, é muito barato criar essencialmente um quadro nulo (que tem todos os dtype, índice, colunas) já configurados.

Não tenho certeza do que você quer dizer com construtor vazio, mas se você quer dizer construir um dataframe sem linhas e o esquema desejado e chamar reindexar, esta é a mesma quantidade de tempo que criar com copy = True.

Sua segunda proposta é razoável, mas apenas se você puder descobrir como fazer Categóricos. Sobre esse assunto, eu estava revisando o código e percebi que Categóricos não são consolidáveis. Então, seguindo um palpite, criei uma matriz de inteiros e duas séries categóricas, criei três DataFrames e concatenei todos os três. Com certeza, ele não executou uma cópia, embora dois dos DataFrames tivessem o mesmo dtype. Vou tentar ver como fazer isso funcionar para o Datetime Index.

@jreback Eu ainda não

@quicknir por que você não mostra uma amostra de código / pseudo-código do que você está realmente tentando fazer.

def read_dataframe(filename, ....):
   f = my_library.open(filename)
   schema = f.schema()
   row_count = f.row_count()
   df = pd.DataFrame.from_empty(schema, row_count)
   dict_of_np_arrays = get_np_arrays_from_DataFrame(df)
   f.read(dict_of_np_arrays)
   return df

O código anterior estava construindo um dicionário de matrizes numpy primeiro e, em seguida, construindo um DataFrame a partir disso, porque estava copiando tudo. Quase metade do tempo estava sendo gasto nisso. Estou tentando mudar para este esquema. A questão é que construir df como acima, mesmo quando você não se preocupa com o conteúdo, é extremamente caro.

@quicknir dict de matrizes np requer muitas cópias.

Você deve simplesmente fazer isso:

# construct your biggest block type (e.g. say you have mostly floats)
df = DataFrame(np.empty((....)),index=....,columns=....)

# then add in other things you need (say strings)
df['foo'] = np.empty(.....)

# say ints
df['foo2'] = np.empty(...)

se você fizer isso pelo tipo d, será barato

então.

for dtype, block in df.as_blocks():
    # fill the values
    block.values[0,0] = 1

já que esses valores de bloco são visualizações em matrizes numerosas

A composição de tipos não é conhecida com antecedência em geral e, no caso de uso mais comum, há uma mistura saudável de floats e ints. Eu acho que não entendi como isso seria barato, se eu tivesse 30 colunas flutuantes e 10 colunas inteiras, então sim, as flutuações seriam muito baratas. Mas quando você faz os ints, a menos que haja alguma maneira de fazê-los todos de uma vez que estou perdendo, cada vez que você adicionar mais uma coluna de ints, todo o bloco int será realocado.

A solução que você me deu anteriormente está perto de funcionar, não consigo fazer com que funcione para DatetimeIndex.

Não tenho certeza do que você quer dizer com construtor vazio, mas se você quer dizer construir um dataframe sem linhas e o esquema desejado e chamar reindexar, esta é a mesma quantidade de tempo que criar com copy = True.

Um construtor vazio pareceria

dtype=np.dtype([('a', np.float64), ('b', np.int64), ('c', np.float32)])
df = pd.DataFrame(columns='abc',index=np.arange(100),dtype=dtype)

Isso produziria a mesma saída que

dtype=np.dtype([('a', np.float64), ('b', np.int64), ('c', np.float32)])
arr = np.empty(100, dtype=dtype)
df = pd.DataFrame.from_records(arr, index=np.arange(100))

apenas não copiava os dados.

Basicamente, o construtor permitiria um dtype misto para a chamada seguinte que funciona, mas apenas um único dtype básico.

df = pd.DataFrame(columns=['a','b','c'],index=np.arange(100), dtype=np.float32)

O único outro _recurso_ seria evitar que ele preenche nulos os arrays int, o que tem o efeito colateral de convertê-los em objeto dtype, uma vez que não há valor ausente para ints.

Sua segunda proposta é razoável, mas apenas se você puder descobrir como fazer Categóricos. Sobre esse assunto, eu estava revisando o código e percebi que Categóricos não são consolidáveis. Então, seguindo um palpite, criei uma matriz de inteiros e duas séries categóricas, criei três DataFrames e concatenei todos os três. Com certeza, ele não executou uma cópia, embora dois dos DataFrames tivessem o mesmo dtype. Vou tentar ver como fazer isso funcionar para o Datetime Index.

O método from_block teria que conhecer as regras de consolidação, de modo que permitiria vários categóricos, mas apenas um dos outros tipos básicos.

sim ... isso não é tão difícil de fazer .... procurando alguém que deseja ter uma introdução gentil aos componentes internos ..... dica.hint.hint .... :)

Haha, estou disposto a fazer algum trabalho de implementação, não me interpretem mal. Tentarei dar uma olhada nas partes internas neste fim de semana e ter uma ideia de qual construtor é mais fácil de implementar. Porém, primeiro eu preciso lidar com alguns problemas de DatetimeIndex que estou tendo em um thread separado.

@quicknir Você encontrou uma solução para isso?

Estou procurando uma maneira de alocar de forma barata (mas não preencher) um dataframe de tipo d misto para permitir o preenchimento sem cópia das colunas de uma biblioteca cython.

Seria ótimo se você estivesse disposto a compartilhar qualquer código que você tem (mesmo que parcialmente funcional) para me ajudar a começar.

O seguinte seria uma abordagem sensata? Eu evitei recriar a lógica de bloqueio trabalhando a partir de um protótipo de dataframe.

Quais dtypes precisam de tratamento especial além dos categóricos?

Claro, usar o dataframe criado não é seguro até que seja preenchido ...

import numpy as np
from pandas.core.index import _ensure_index
from pandas.core.internals import BlockManager
from pandas.core.generic import NDFrame
from pandas.core.frame import DataFrame
from pandas.core.common import CategoricalDtype
from pandas.core.categorical import Categorical
from pandas.core.index import Index

def allocate_like(df, size, keep_categories=False):
    # define axes (waiting for #939 (RangeIndex))
    axes = [df.columns.values.tolist(), Index(np.arange(size))]

    # allocate and create blocks
    blocks = []
    for block in df._data.blocks:
        # special treatment for non-ordinary block types
        if isinstance(block.dtype, CategoricalDtype):
            if keep_categories:
                categories = block.values.categories
            else:
                categories = Index([])
            values = Categorical(values=np.empty(shape=block.values.shape,
                                                 dtype=block.values.codes.dtype),
                                 categories=categories,
                                 fastpath=True)
        # ordinary block types
        else:
            new_shape = (block.values.shape[0], size)
            values = np.empty(shape=new_shape, dtype=block.dtype)

        new_block = block.make_block_same_class(values=values,
                                                placement=block.mgr_locs.as_array)
        blocks.append(new_block)

    # create block manager
    mgr = BlockManager(blocks, axes)

    # create dataframe
    return DataFrame(mgr)


# create a prototype dataframe
import pandas as pd
a = np.empty(0, dtype=('i4,i4,f4,f4,f4,a10'))
df = pd.DataFrame(a)
df['cat_col'] = pd.Series(list('abcabcdeff'), dtype="category")

# allocate an alike dataframe
df1 = allocate_like(df, size=10)

@ ARF1 não tenho certeza de qual é o objetivo final
você pode fornecer um exemplo simples?

concat posteriormente com copy = False geralmente contornará isso

@jreback Eu quero usar uma biblioteca cython para ler grande volume de dados coluna por coluna de um armazenamento de dados compactado que desejo descompactar diretamente em um dataframe sem cópia intermediária por motivos de desempenho.

Pegando emprestado a solução numpy usual em tais casos, eu quero pré-alocar a memória para um dataframe para que eu possa passar ponteiros para essas regiões de memória alocadas para minha biblioteca de cython, que pode então usar ponteiros c / matrizes c comuns correspondentes a essas regiões de memória para preencher o dataframe diretamente, sem etapas intermediárias de cópia (ou a geração de objetos intermediários de python). A opção de preencher os dataframes com vários threads de cython em paralelo com gil liberado seria um benefício adicional.

No pseudocódigo (simplificado), o idom seria algo como:

df = fn_to_allocate_memory()
colums = df.columns.values
column_indexes = []
for i in xrange(len(df._data.blocks)):
    column_indexes.extend(df._data.blocks[i].mgr_locs.as_array)
block_arrays = [df._data.blocks[i].values for i in len(df._data.blocks)]

some_cython_library.fill_dataframe_with_content(columns, column_indexes, block_arrays)

Isso faz algum sentido para você?

Pelo que entendi concat com copy=False não aglutinará colunas com dtypes idênticos em blocos, mas operações abaixo da linha irão acionar isso - resultando na cópia que estou tentando evitar. Ou eu entendi mal o funcionamento interno dos pandas?

Embora eu tenha feito algum progresso com a instanciação de grandes dataframes (não preenchidos) (fator ~ 6,7), ainda estou longe de velocidades entorpecidas. Apenas mais um fator de ~ 90 para ir ...

In [157]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10'))

In [158]: df = pd.DataFrame(a)

In [162]: %timeit np.empty(int(1e6), dtype=('i8,i4,i4,f4,f4,f4,a10'))
1000 loops, best of 3: 247 µs per loop

In [163]: %timeit allocate_like(df, size=int(1e6))
10 loops, best of 3: 22.4 ms per loop

In [164]: %timeit pd.DataFrame(np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10')))

10 loops, best of 3: 150 ms per loop

Outra esperança era que essa abordagem também pudesse permitir a instanciação repetida mais rápida de DataFrames de formato idêntico quando dados de pequeno volume fossem lidos com frequência. Esse não foi o objetivo principal até agora, mas inadvertidamente fiz um melhor progresso com isso: apenas um fator de ~ 4,8 para chegar a uma velocidade entorpecente.

In [157]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4,a10'))

In [158]: df = pd.DataFrame(a)

In [159]: %timeit np.empty(0, dtype=('i8,i4,i4,f4,f4,f4,a10'))
10000 loops, best of 3: 79.9 µs per loop

In [160]: %timeit allocate_like(df, size=0)
1000 loops, best of 3: 379 µs per loop

In [161]: %timeit pd.DataFrame(np.empty(0, dtype=('i4,i4,f4,f4,f4,a10')))
1000 loops, best of 3: 983 µs per loop

Editar

Os tempos acima estão pintando um quadro pessimista demais ao comparar maçãs com laranjas: enquanto a coluna numpy string é criada como strings nativas de comprimento fixo, a coluna equivalente em pandas será criada como um array de objetos python. Comparar de forma semelhante empurra a instanciação do DataFrame para velocidades numpy, com exceção da geração do índice, que é responsável por cerca de 92% do tempo de instanciação.

@ ARF1 se você quiser velocidades numpy, então use numpy. Não tenho certeza do que você está realmente fazendo ou do que está fazendo no cython. As soluções usuais são dividir seus cálculos, passar tipos d simples para cython ou apenas obter uma máquina maior.

DataFrames fazendo muito mais do que entorpecente sobre como eles descrevem e manipulam dados. Não é o que você está realmente fazendo com eles.

quase todas as operações do pandas são copiadas. (como a maioria das operações numpy), portanto, não tenho certeza do que você está procurando.

@jreback No momento, estou usando o numpy, mas misturei dtypes que só podem ser (convenientemente) manipulados com matrizes estruturadas. Matrizes estruturadas, entretanto, são inerentemente ordenadas pela linha principal, o que entra em conflito com minha dimensão de análise típica, levando a um desempenho ruim. O Pandas parece a alternativa natural devido à sua ordem principal das colunas - se eu conseguir inserir os dados no dataframe em uma boa velocidade.

É claro que a alternativa seria usar um ditado de matrizes numpy com dipos diferentes, mas isso torna a análise uma dor, já que fatiar, etc., não é mais possível.

As soluções usuais são dividir seus cálculos, passar tipos d simples para cython.

Isso é o que estou fazendo com a variável block_arrays em meu exemplo.

ou apenas compre uma máquina maior.

Um fator 100+ mais rápido é um desafio financeiro para mim. ;-)

@ ARF1 você tem um modelo muito estranho de como as coisas funcionam. Normalmente, você cria um pequeno número de quadros de dados e, a seguir, trabalha neles. A velocidade de criação é uma pequena fração de qualquer computação ou manipulação real.

@jreback : este não é um modelo estranho. Talvez seja um modelo estranho se você ver as coisas de uma perspectiva python pura. Se você estiver trabalhando com código C ++, a maneira mais fácil de ler dados em objetos Python é passar ponteiros para objetos Python pré-existentes. Se você estiver fazendo isso em um contexto sensível ao desempenho, você deseja uma maneira barata e estável (no sentido de localização da memória) de criar o objeto Python.

Sinceramente, não sei por que essa atitude é comum nas placas dos pandas. Eu acho que é lamentável, na medida em que eu entendo que os pandas são uma construção de nível superior do que entorpecidos, ainda poderia ser mais fácil para as pessoas se desenvolverem "em cima" dos pandas. O DataFrame do pandas é de longe o tipo mais desejável de se trabalhar se você tiver um código C que queira inserir dados tabulares em python, então esse realmente parece um caso de uso importante.

Por favor, não leve o que estou escrevendo de forma negativa, se eu não achasse os pandas DataFrames tão incríveis, eu apenas usaria registros numpy ou algo parecido e pronto.

@ ARF1 : Em última análise, não me lembro dos motivos, mas o melhor que consegui fazer foi criar um DataFrame para cada tipo numérico a partir de uma matriz numpy com Copy = False e, em seguida, usar pandas.concat com Copy = False novamente para concatená-los. Ao criar um DataFrame de tipo único a partir de uma matriz numpy, tome muito cuidado com a orientação da matriz numpy. Se a orientação estiver errada, então as matrizes numpy correspondentes a cada coluna terão passos não triviais, e os pandas não gostam disso e farão uma cópia na primeira oportunidade. Você pode adicionar os Categóricos no final, pois eles não são consolidados e não devem acionar nenhuma cópia do resto do quadro.

Eu recomendo escrever alguns testes de unidade que realizam esta operação passo a passo e continuamente pegam os ponteiros para os dados subjacentes (via array_interface do array numpy subjacente) e verifique se eles são os mesmos para garantir que a cópia esteja realmente sendo eliminada. É uma decisão muito infeliz dos pandas que os parâmetros de copiar / inserir NÃO precisam ser respeitados. Ou seja, mesmo se você definir, por exemplo, copy = False para um construtor DataFrame, o pandas ainda executará uma cópia se decidir que precisa para construir o DataFrame. O fato de que o pandas faz isso em vez de jogar quando os argumentos não podem ser honrados torna a escrita confiável de código que elimina cópias muito cansativa e exige ser extremamente metódico. Se você não escrever testes de unidade para verificar, você pode acidentalmente ajustar algo mais tarde que faz com que uma cópia seja feita e isso acontecerá silenciosamente e arruinará seu desempenho.

@quicknir se você diz. Acho que você deve simplesmente traçar o perfil antes de tentar otimizar as coisas. Como eu disse antes, e prob fará novamente. O tempo de construção não deve dominar nada. Em caso afirmativo, você está apenas usando o DataFrame para manter as coisas, então qual é o ponto de usá-lo em primeiro lugar? Se não dominar, qual é o problema?

@jreback Você escreve isso, supondo que eu ainda não tenha feito o perfil. Na verdade, eu tenho. Temos código c ++ e python que desserializam dados tabulares do mesmo formato de dados. Embora eu esperasse que o código Python tivesse um pouco de sobrecarga, achei que a diferença deveria ser pequena, porque o tempo de leitura do disco deveria dominar. Este não era o caso, antes que eu retrabalhasse as coisas com extremo cuidado para minimizar as cópias, a versão python estava demorando o dobro ou pior em comparação com o código C ++, e quase todo o overhead estava apenas na criação do DataFrame. Em outras palavras, demorou quase tanto tempo para simplesmente criar um DataFrame de um certo tamanho muito grande, cujo conteúdo eu não me importava, quanto ler, descompactar e gravar os dados que eu queria naquele DataFrame. É um desempenho extremamente fraco.

Se eu fosse um usuário final deste código com operações específicas em mente, talvez o que você está dizendo sobre a construção não dominar fosse válido. Na verdade, sou um desenvolvedor e os usuários finais desse código são outras pessoas. Não sei exatamente o que eles farão com o DataFrame, o DataFrame é a única forma de obter uma representação na memória dos dados no disco. Se eles querem fazer algo muito simples com os dados do disco, eles ainda precisam passar pelo formato DataFrame.

Obviamente, eu poderia oferecer suporte a mais maneiras de obter os dados (por exemplo, construções numpy), mas isso aumentaria muito a ramificação do código e tornaria as coisas muito mais difíceis para mim como desenvolvedor. Se houvesse alguma razão fundamental pela qual os DataFrames precisam ser tão lentos, eu entenderia e decidiria se devo oferecer suporte a DataFrame, numpy ou ambos. Mas não há nenhuma razão real para que seja tão lento. Pode-se escrever um método DataFrame.empty que recebe uma matriz de tuplas, onde cada tupla contém o nome e o tipo da coluna e o número de linhas.

Esta é a diferença que quero dizer entre apoiar usuários e escritores de biblioteca. É mais fácil escrever seu próprio código do que escrever uma biblioteca. E é mais fácil ter sua biblioteca suportando apenas usuários em vez de outros escritores de biblioteca. Só acho que, neste caso, a alocação vazia de DataFrames seria um fruto fácil para os pandas, o que tornaria a vida de pessoas como eu e @ ARF1 mais fácil.

bem, se você gostaria de ter um soln documentado testado razoável, todos os ouvidos. O pandas tem alguns usuários / desenvolvedores. Essa é a razão pela qual o DataFrame é tão versátil e a mesma razão pela qual ele precisa de muitas verificações de erros e inferências. Você está convidado a ver o que pode fazer conforme descrito acima.

Estou disposto a dedicar algum tempo para implementar isso, mas apenas se houver algum consenso razoável sobre o design de alguns dos desenvolvedores do pandas. Se eu enviar uma solicitação pull e houver certas coisas que as pessoas desejam alterar, tudo bem. Ou se eu perceber, depois de dedicar dez horas a isso, que não há como fazer algo de maneira limpa e que a única maneira de fazer isso pode envolver algo que as pessoas acham questionável, isso também é legal. Mas eu não estou muito bem em gastar X horas e ouvir que isso não é tão útil, a implementação é complicada, não achamos que possa realmente ser limpo, complica a base de código, etc. Não sei se Estou muito confuso com esse sentimento, não fiz grandes contribuições para um projeto de OSS antes, então não sei como funciona. Só que na minha postagem inicial comecei propondo exatamente isso e, então, francamente, tive a impressão de que estava meio "fora do escopo" para os pandas.

Se você quiser, posso abrir um novo fascículo, criar uma proposta de design tão específica quanto possível e, assim que houver feedback / aprovação provisória, vou trabalhar nisso quando puder.

@quicknir o principal é que ele deve passar por todo o conjunto de testes, que é bastante abrangente.

Isso não está fora do escopo do pandas, mas a API deve ser um tanto amigável.

Eu não tenho certeza porque você não gostou

concat(list_of_arrays,axis=1,copy=False) Eu acredito que isso faz exatamente o que você quer (e se não, não está claro o que você realmente quer).

Acabei usando uma técnica semelhante, mas com uma lista de DataFrames que foram criados a partir de um único array numpy, cada um de tipos diferentes.

Em primeiro lugar, acho que ainda encontrei algumas cópias quando fiz essa técnica. Como eu disse, o pandas nem sempre honra copy = False, então é muito cansativo ver se o seu código está realmente copiando ou não. Eu realmente desejo que, para o pandas 17, os desenvolvedores considerem fazer copy = True o padrão e, em seguida, copy = False atire quando uma cópia não puder ser omitida. Mas de qualquer maneira.

Em segundo lugar, outro problema era ter que reordenar as colunas posteriormente. Isso foi surpreendentemente estranho, a única maneira que encontrei de fazer isso sem fazer uma cópia foi originalmente fazer os nomes das colunas inteiros que foram ordenados na ordem final desejada. Em seguida, fiz uma classificação de índice no local. Em seguida, mudei os nomes das colunas.

Terceiro, descobri que as cópias eram inevitáveis ​​para tipos de carimbo de data / hora (numpy datetime64).

Escrevi este código há algum tempo, por isso não estou fresco em minha mente. É possível que eu tenha cometido erros, mas analisei com muito cuidado e esses foram os resultados que obtive na época.

O código fornecido acima nem mesmo funciona para matrizes numpy. Ele falha com: TypeError: não é possível concatenar um objeto não-NDFrame. Você tem que torná-los DataFrames primeiro.

Não é que eu não goste da solução que você deu aqui, ou acima. Ainda estou para ver um simples que funcione.

@quicknir bem, meu exemplo acima funciona. pls fornecer exatamente o que você está fazendo e posso tentar ajudá-lo.

pd.concat ([np.zeros ((2,2))], eixo = 1, copiar = Falso)

Estou no pandas 0.15.2, talvez isso tenha começado a funcionar no 0.16?

por favor, leia a string doc de pd.concat . você precisa passar um DataFrame

aliás copy=True É o padrão

Certo, foi isso que eu escrevi. O trecho de código que você escreveu acima tinha list_of_arrays, não list_of_dataframes. De qualquer forma, acho que nos entendemos. Acabei usando o método pd.concat, mas não é nada trivial, há um monte de pegadinhas para enganar as pessoas:

1) Você deve criar uma lista de DataFrames. Cada DataFrame deve ter exatamente um dtype distinto. Portanto, você deve coletar todos os diferentes tipos de d antes de começar.

2) Cada DataFrame deve ser criado a partir de um único array numpy do tipo d desejado, mesmo número de linhas, número de colunas desejado e ordem = sinalizador 'F'; se pedido = 'C' (padrão), então os pandas freqüentemente farão cópias quando de outra forma não fariam.

3) Desconsiderar 1) para Categóricos, eles não são agrupados em um bloco para que você possa juntá-los mais tarde.

4) Quando você cria todos os DataFrames individuais, as colunas devem ser nomeadas usando inteiros que representam a ordem em que você deseja. Caso contrário, pode não haver maneira de alterar a ordem das colunas sem disparar cópias.

5) Tendo criado sua lista de DataFrames, use concat. Você terá que verificar meticulosamente se não bagunçou nada, porque copy = False não será lançada se uma cópia não puder ser omitida, mas sim uma cópia silenciosa.

6) Classifique o índice da coluna para obter a ordem desejada e, em seguida, renomeie as colunas.

Eu apliquei esse procedimento com rigor. Não é uma linha única, há muitos lugares para cometer erros, tenho quase certeza de que ainda não funcionou para carimbos de data / hora e há muito overhead desnecessário que poderia ser eliminado por não usar apenas a interface. Se você quiser, posso escrever um rascunho de como essa função se parece usando apenas a API pública, talvez combinada com alguns testes para ver se está realmente eliminando cópias e para quais dtypes.

Além disso, copy = False é o padrão para, por exemplo, o construtor DataFrame. Meu ponto principal é mais que uma função que não pode honrar seus argumentos deve lançar ao invés de "fazer algo razoável". Ou seja, se copy = False não puder ser honrado, uma exceção deve ser lançada para que o usuário saiba que deve alterar outras entradas para que a elisão de cópia possa ocorrer ou alterar a cópia para True. Uma cópia nunca deve acontecer silenciosamente quando copy = False, isso é mais surpreendente e menos propício para um usuário preocupado com o desempenho encontrar bugs.

você tem muitos passos aqui que não são necessários
por favor, mostre um exemplo real como fiz acima

você entende que uma visão entorpecida pode retornar uma cópia por operações de remodelagem muito simples (às vezes) e não outras

sempre haverá uma garantia suave na cópia, já que geralmente não é possível sem muita introspecção para garantir isso, o que, por definição, anula o propósito de um código simples e poderoso de desempenho

O comportamento de copy=False na construção do DataFrame é consistente com a função np.array numpy (por exemplo, se você fornecer uma lista de arrays, os dados finais sempre farão uma cópia).

Parece que esta é uma lacuna de recursos infeliz em pandas. IMHO, nunca vamos ter uma solução satisfatória com o modelo atual para pandas internos (que consolida blocos). Infelizmente, isso não é realmente uma opção para pandas, porque pandas ainda precisa funcionar para pessoas que fazem DataFrames com muitas colunas.

O que precisamos é uma implementação alternativa de DataFrame projetada especificamente para trabalhar com dados organizados que armazene cada coluna independentemente como matrizes numpy 1D. Na verdade, isso é um tanto semelhante ao modelo de dados em xray , exceto que permitimos que as colunas sejam matrizes N-dimensionais.

Acredito que um construtor geral de dataframe pandas de alto desempenho que apenas aloca espaço não seja trivial, dada a ampla gama de diferentes tipos de coluna que ele precisaria oferecer suporte.

Dito isso, parece bastante simples para os escritores de bibliotecas que desejam usar os dataframes do pandas como contêineres de dados de alto desempenho para implementar um construtor de dataframe apenas de alocação limitado aos tipos de coluna de que precisam.

O seguinte segmento de código pode servir de inspiração. Ele permite a instanciação de dataframes apenas de alocação e não preenchidos em velocidades próximas de entorpecentes. Observe que o código requer PR # 9977:

import numpy as np
from pandas.core.index import _ensure_index
from pandas.core.internals import BlockManager
from pandas.core.generic import NDFrame
from pandas.core.frame import DataFrame
from pandas.core.common import CategoricalDtype
from pandas.core.categorical import Categorical
from pandas.core.index import RangeIndex

def allocate_like(df, size, keep_categories=False):
    # define axes (uses PR #9977)
    axes = [df.columns.values.tolist(), RangeIndex(size)]

    # allocate and create blocks
    blocks = []
    for block in df._data.blocks:
        # special treatment for non-ordinary block types
        if isinstance(block.dtype, CategoricalDtype):
            if keep_categories:
                categories = block.values.categories
            else:
                categories = Index([])
            values = Categorical(values=np.empty(shape=block.values.shape,
                                                 dtype=block.values.codes.dtype),
                                 categories=categories,
                                 fastpath=True)
        # ordinary block types
        else:
            new_shape = (block.values.shape[0], size)
            values = np.empty(shape=new_shape, dtype=block.dtype)

        new_block = block.make_block_same_class(values=values,
                                                placement=block.mgr_locs.as_array)
        blocks.append(new_block)

    # create block manager
    mgr = BlockManager(blocks, axes)

    # create dataframe
    return DataFrame(mgr)

Com o construtor de exemplo allocate_like() a penalidade de desempenho cf numpy é apenas x2,3 (normalmente x333) para matrizes grandes e x3,3 (normalmente x8,9) para matrizes de tamanho zero:

In [2]: import numpy as np

In [3]: import pandas as pd

In [4]: a = np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4'))

# create template-dataframe
In [5]: df = pd.DataFrame(a)

# large dataframe timings
In [6]: %timeit np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4'))
1000 loops, best of 3: 212 µs per loop

In [7]: %timeit allocate_like(df, size=int(1e6))
1000 loops, best of 3: 496 µs per loop

In [8]: %timeit pd.DataFrame(np.empty(int(1e6), dtype=('i4,i4,f4,f4,f4')))
10 loops, best of 3: 70.6 ms per loop

# zero-size dataframe timing
In [9]: %timeit np.empty(0, dtype=('i4,i4,f4,f4,f4'))
10000 loops, best of 3: 108 µs per loop

In [10]: %timeit allocate_like(df, size=0)
1000 loops, best of 3: 360 µs per loop

In [11]: %timeit pd.DataFrame(np.empty(0, dtype=('i4,i4,f4,f4,f4')))
1000 loops, best of 3: 959 µs per loop

Desculpe, eu perdi isso por um tempo. @ ARF1 , muito obrigado pelo exemplo de código acima. Muito bom, junto com as métricas de desempenho.

Eu realmente sinto que criar uma classe que corresponda ao layout do DataFrame, sem nenhum dos dados, tornará o código acima muito mais natural, e talvez também com mais desempenho. Essa classe também pode ser reutilizada, por exemplo, ao fazer a reindexação das linhas.

O que eu basicamente proponho é algo assim: uma classe chamada DataFrameLayout, que envolve os dtypes, nomes de colunas e ordenação de colunas. Por exemplo, ele pode armazenar um dicionário de dtype para números de coluna (para ordenação) e um array separado com todos os nomes. A partir desse layout, você pode ver que uma iteração simples e elegante sobre o dicionário permitiria criar os gerenciadores de bloco rapidamente. Essa classe pode então ser usada em lugares como um construtor vazio ou em operações de reindexação.

Acho que essas abstrações são necessárias para dados mais complexos. Em certo sentido, DataFrame é um tipo de dados composto e um DataFrameLayout especificaria a natureza exata da composição.

A propósito, acho que algo semelhante é necessário para categóricos; ou seja, é necessário que haja uma abstração CategoricalType que armazene as categorias, estejam ou não ordenadas, o tipo de matriz de apoio, etc. Ou seja, tudo, exceto os dados reais. Na verdade, quando você pensa em um DataFrameLayout, percebe que todas as colunas devem ter tipos totalmente especificados e isso é atualmente problemático para Categóricos.

O que as pessoas pensam dessas duas classes?

@quicknir Já temos uma classe CategoricalDtype - concordo que ela poderia ser estendida para a CategoricalType você descreve.

Não estou totalmente certo sobre a classe DataFrameLayout . Fundamentalmente, acho que poderíamos usar um modelo de dados alternativo e mais simples para quadros de dados (mais semelhante a como são feitos em R ou Julia). Há algum interesse nesse tipo de coisa e suspeito que isso vá acontecer de alguma forma, mas provavelmente não tão cedo (e talvez nunca como parte do projeto dos pandas).

@quicknir yeh, DataFrameLayout está reinventando a roda aqui. Já temos uma especificação de tipo d, por exemplo

In [14]: tm.makeMixedDataFrame().to_records().dtype
Out[14]: dtype([('index', '<i8'), ('A', '<f8'), ('B', '<f8'), ('C', 'O'), ('D', '<M8[ns]')])

@jreback Não é reinventar a roda, já que a especificação dtype tem vários problemas importantes:

1) Pelo que eu posso ver, to_records () fará uma cópia detalhada de todo o DataFrame. Obter a especificação (usarei esse termo de agora em diante) para o DataFrame deve ser barato e fácil.

2) A saída de to_records é do tipo numpy. Uma implicação disso é que não vejo como isso poderia ser estendido para oferecer suporte adequado para Categóricos.

3) Este método de armazenamento interno da especificação não é facilmente compatível com a forma como os dados são armazenados dentro do DataFrame (ou seja, em blocos do tipo dtipo). Criar os blocos a partir de tal especificação envolve muito trabalho extra que pode ser eliminado ao armazenar a especificação de uma maneira como sugeri, com um ditado de dtype para números de coluna. Quando você tem um DataFrame com 2.000 colunas, isso sai caro.

Em suma, o dtype da representação do registro é mais uma solução alternativa para a falta de uma especificação adequada. Ele carece de vários recursos principais e tem um desempenho muito pior.

Existem muitos tópicos no SO solicitando esse recurso.

Parece-me que todos esses problemas decorrem do BlockManager consolidando colunas separadas em um único bloco de memória (os 'blocos').
A correção mais fácil não seria não consolidar dados em blocos quando copy = False for especificado.

Eu tenho um BlockManager corrigido por macaco que não consolida:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
que usei para contornar esse problema.

Esta página foi útil?
0 / 5 - 0 avaliações