Pandas: No hay forma de construir DataFrame de dtype mixto sin copia total, solución propuesta

Creado en 9 ene. 2015  ·  58Comentarios  ·  Fuente: pandas-dev/pandas

Después de horas de arrancarme el pelo, he llegado a la conclusión de que es imposible crear un DataFrame de tipo dtype mixto sin copiar todos sus datos. Es decir, no importa lo que hagas, si quieres crear un DataFrame de dtype mixto , inevitablemente creará una versión temporal de los datos (por ejemplo, utilizando np.empty), y los diversos constructores de DataFrame siempre harán copias de este temporal. Este problema ya se mencionó hace un año: https://github.com/pydata/pandas/issues/5902.

Esto es especialmente terrible para la interoperabilidad con otros lenguajes de programación. Si planea completar los datos en el DataFrame desde, por ejemplo, una llamada a C, la forma más fácil de hacerlo es crear el DataFrame en python, obtener punteros a los datos subyacentes, que son np.arrays, y pasar estos np .arrays para que se puedan completar. En esta situación, simplemente no le importa con qué datos comienza el DataFrame, el objetivo es simplemente asignar la memoria para que sepa a qué está copiando.

Esto también es generalmente frustrante porque implica que, en principio (dependiendo potencialmente de la situación específica, y los detalles de implementación, etc.) es difícil garantizar que no terminará usando el doble de memoria que realmente debería.

Esto tiene una solución extremadamente simple que ya se basa en la pila cuantitativa de Python: tenga un método análogo al de numpy vacío. Esto asigna el espacio, pero en realidad no pierde tiempo escribiendo o copiando nada. Dado que el vacío ya está tomado, propondría llamar al método from_empty. Aceptaría un índice (obligatorio, el caso de uso más común sería pasar np.arange (N)), columnas (obligatorio, normalmente una lista de cadenas), tipos (lista de tipos aceptables para columnas, la misma longitud que las columnas). La lista de tipos debe incluir soporte para todos los tipos numéricos numerosos (entradas, flotantes), así como columnas especiales de Pandas como DatetimeIndex y Categorical.

Como ventaja adicional, dado que la implementación se realiza en un método completamente separado, no interferirá en absoluto con la API existente.

API Design Constructors Dtypes

Comentario más útil

Hay muchos hilos en SO que solicitan esta función.

Me parece que todos estos problemas provienen de BlockManager que consolida columnas separadas en un solo fragmento de memoria (los 'bloques').
¿No sería la solución más fácil no consolidar los datos en bloques cuando se especifica copy = False?

Tengo un BlockManager parcheado con mono que no se consolida:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
que solía solucionar este problema.

Todos 58 comentarios

simplemente puede crear un marco vacío con un índice y columnas
luego asigne ndarrays: estos no copiarán de usted asigna todo un tipo de d en particular a la vez

puede crear estos con np.empty si lo desea

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 me está copiando. A menos que el código que escribí no sea lo que quisiste decir, o que la copia que ocurrió no sea la copia que pensaste que estaba tratando de eludir.

cambiaste el tipo d
por eso copió probar con un flotador

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

El dtype es un objeto, por lo que cambia independientemente de si uso un flotante o un 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

Se puede construir sin una copia en tipo mixto, pero es bastante complicado. El problema es que algunos tipos requieren una copia (por ejemplo, un objeto para evitar problemas de contención de memoria). Y la estructura interna consolida diferentes tipos, por lo que agregar un nuevo tipo requerirá una copia. Evitar una copia es bastante difícil en la mayoría de los casos.

Solo debe crear lo que necesita, obtener punteros a los datos y luego sobrescribirlos. ¿Por que eso es un problema?

El problema es que para crear lo que necesito, tengo que copiar en material del tipo correcto, cuyos datos no tengo intención de usar. Incluso suponiendo que su sugerencia de crear un DataFrame vacío no use RAM significativa, esto no alivia el costo de la copia. Si quiero crear un DataFrame de 1 gigabyte y poblarlo en otro lugar, tendré que pagar el costo de copiar un gigabyte de basura en la memoria, lo cual es completamente innecesario. ¿No ve esto como un problema?

Sí, entiendo que la estructura interna consolida diferentes tipos. No estoy seguro de qué quiere decir exactamente con problemas de contención de memoria, pero en cualquier caso, los objetos no son realmente lo que interesa aquí.

En realidad, si bien evitar las copias en general es un problema difícil, evitarlas de la manera que sugerí es bastante fácil porque estoy proporcionando toda la información necesaria desde el principio. Es idéntico a construir a partir de datos, excepto que en lugar de inferir los dtypes y el número de filas de los datos y copiar los datos, especifica los dtypes y el número de filas directamente y hace todo lo demás exactamente como lo hubiera hecho menos la copia.

Necesita un constructor "vacío" para cada tipo de columna compatible. Para tipos numéricos numerosos, esto es obvio, necesita un trabajo distinto de cero para categórico, no estoy seguro de DatetimeIndex.

pasar un dictado al constructor y copiar = Falso debería funcionar

Entonces esto funcionará. Pero debe estar SEGURO de que las matrices que está pasando sean tipos distintos. Y una vez que haga algo, podría copiar los datos subyacentes. Entonces YMMV. por supuesto, puede pasar np.empty lugar de los unos / ceros que soy.

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)

_El intento inicial se eliminó porque no funciona porque reindex fuerza el casting, que es una "característica" extraña ._

Tengo que usar 'método', lo que hace que este intento sea un poco menos satisfactorio:

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

Si está realmente preocupado por el rendimiento, no estoy seguro de por qué uno no usaría numpy tanto como sea posible, ya que es conceptualmente mucho más simple.

jreback, gracias por tu solución. Esto parece funcionar, incluso para categóricos (lo que me sorprendió). Si encuentro problemas, te lo haré saber. No estoy seguro de lo que quiere decir con: si hace algo con esto, podría copiar. ¿Qué quieres decir con algo? A menos que haya semántica COW, creo que lo que ves es lo que obtienes con respecto a las copias profundas frente a las superficiales, en el momento de la construcción.

Sigo pensando que debería implementarse un constructor from_empty, y no creo que sea tan difícil, aunque esta técnica funciona, implica mucha sobrecarga de código. En principio, esto podría hacerse especificando un solo dtype compuesto y varias filas.

bashtage, estas soluciones todavía escriben en todo el DataFrame. Dado que la escritura es generalmente más lenta que la lectura, esto significa que, en el mejor de los casos, se ahorra menos de la mitad de los gastos generales en cuestión.

Obviamente, si no he usado numpy, es porque los pandas tienen muchas características y capacidades increíbles que me encantan, y no quiero renunciar a ellas. ¿Realmente estabas preguntando, o simplemente insinuando que debería usar numpy si no quiero recibir este golpe de rendimiento?

Borre esto, por favor, error de usuario y mis disculpas. reindex_axis con copy = False funcionó perfectamente.

bashtage, estas soluciones todavía escriben en todo el DataFrame. Dado que la escritura es generalmente más lenta que la lectura, esto significa que, en el mejor de los casos, se ahorra menos de la mitad de los gastos generales en cuestión.

Es cierto, pero todo lo que necesita es un nuevo method por reindex que no se llenará con nada y luego puede asignar una matriz escrita con tipos de columna arbitrarios sin escribir / copiar.

Obviamente, si no he usado numpy, es porque los pandas tienen muchas características y capacidades increíbles que me encantan, y no quiero renunciar a ellas. ¿Realmente estabas preguntando, o simplemente insinuando que debería usar numpy si no quiero recibir este golpe de rendimiento?

Fue un poco retórico, aunque también una sugerencia seria desde el punto de vista del rendimiento, ya que numpy hace que sea mucho más fácil acercarse al acceso a datos como una gota de memoria que es importante si está tratando de escribir muy código de alto rendimiento. Siempre puede convertir de numpy a pandas cuando la simplicidad del código es más importante que el rendimiento.

Veo lo que dices. Sigo pensando que debería ser parte de la interfaz de forma más limpia en lugar de una solución alternativa, pero a medida que avanzan las soluciones, es una buena y fácil de implementar.

Pandas todavía enfatiza el desempeño como uno de sus principales objetivos. Obviamente, tiene características de mayor nivel en comparación con Numpy, y esas tienen que pagarse. De lo que estamos hablando no tiene nada que ver con esas características de nivel superior, y no hay ninguna razón por la que uno deba pagar copias masivas en lugares donde no las necesita. Su sugerencia sería apropiada si alguien estuviera molestando por el costo de configurar las columnas, el índice, etc., que es completamente diferente de esta discusión.

Creo que está sobrestimando el costo de escritura frente al código de asignación de memoria en Python; la parte más cara es la asignación de memoria. La creación de objetos también es cara.

Ambos asignan 1 GB de memoria, uno vacío y uno ceros.

%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 poner a cero 150.000.000 valores.

Ahora compare estos para un DataFrame trivial.

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

Alrededor de 200 veces más lento para trivial. Pero es mucho peor para arreglos más grandes.

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

Ahora se necesitan 275 m s; tenga en cuenta que esto no está copiando nada. El costo está en configurar el índice, etc., que claramente es muy lento cuando la matriz no es trivialmente grande.

Esto me parece una optimización prematura, ya que los otros gastos generales en pandas son tan grandes que el componente de relleno malloc + tiene un costo cercano a 0.

Parece que si desea asignar algo en un bucle cerrado, debe ser una matriz numerosa por razones de rendimiento.

ok, esto es lo que creo que deberíamos hacer, @quicknir si desea hacer algunas mejoras. 2 números.

  • # 4464: esto es esencialmente permitir un dtype compuesto en el constructor DataFrame y luego dar la vuelta y llamar a from_records() , que también se puede llamar si la matriz pasada es una matriz rec / estructurada - esto Básicamente haría from_records la ruta de procesamiento de la matriz rec / estructurada
  • pasar a través de la palabra clave copy= a from_records
  • from_records puede usar el concat soln que muestro arriba, en lugar de dividir el rec-array, desinfectarlos (como series) y luego volverlos a unir (en bloques dtype; esta parte se hace internamente).

Esto es un poco no trivial, pero luego permitiría pasar un ndarray ya creado (podría estar vacío) con tipos mixtos con bastante facilidad. Tenga en cuenta que esto probablemente (en una implementación de primer paso) solo manejaría (int / float / string). ya que datetime / timedelta necesitan una desinfección especial y haría esto un poco más complicado.

por lo que @bashtage tiene razón desde una perspectiva de

Lo que quise decir arriba es esto. Pandas agrupa cualquier tipo-d (por ejemplo, int64, int32 son diferentes) en un 'bloque' (2-d en un marco). Se trata de un ndarray de memoria contiguo (que se ha asignado recientemente, a menos que simplemente se pase en el que solo funciona actualmente para un solo dtype). Si luego hace un setitem, por ejemplo, df['new_columns'] = 5 y ya tiene un bloque int64, esta nueva columna será concatenada en última instancia (resultando en una nueva asignación de memoria para ese dtype). Si estaba utilizando una referencia como vista sobre esto, ya no será válida. Es por eso que esta no es una estrategia que pueda emplear sin mirar los componentes internos de DataFrame.

@bashtage yeh, el gran costo es el índice, como ha señalado. a RangeIndex (ver # 939) resolvería este problema por completo. (en realidad está casi hecho en una rama lateral, solo necesita un poco de polvo).

Incluso con un RangeIndex optimizado, seguirá siendo 2 órdenes de magnitud más lento que construir una matriz NumPy, lo cual es bastante justo dada la naturaleza mucho más pesada y las capacidades adicionales de DataFrame .

Creo que esto solo puede considerarse una función de conveniencia y no un problema de rendimiento. Podría ser útil inicializar un tipo mixto DataFrame o 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])

esto es solo un problema de API / conveniencia

acordó que el rendimiento es realmente un problema incidental (y no el controlador)

@bashtage

% timeit pd.DataFrame (np.empty ((100, 1000000)))
100 bucles, lo mejor de 3: 15,6 ms por bucle

% timeit pd.DataFrame (np.empty ((100, 1000000)), copy = True)
1 bucle, lo mejor de 3: 302 ms por bucle

Por lo tanto, copiar en un marco de datos parece llevar 20 veces más tiempo que todo el resto del trabajo involucrado en la creación del marco de datos, es decir, la copia (y la asignación adicional) es el 95% del tiempo. Los puntos de referencia que hizo no compararon lo correcto. No importa si la copia en sí o la asignación es lo que está tomando tiempo, el punto es que si pudiera evitar las copias para un DataFrame de dtype múltiple de la manera que puedo para un DataFrame de dtype único, podría ahorrar una gran cantidad de tiempo.

Su razonamiento de dos órdenes de magnitud también es engañoso. Esta no es la única operación que se realiza, se están realizando otras operaciones que llevan tiempo, como las lecturas de disco. En este momento, la copia adicional que necesito hacer para crear el DataFrame está tomando aproximadamente la mitad del tiempo en mi programa simple que solo lee los datos fuera del disco y en un DataFrame. Si tomara 1/20 del tiempo, entonces la lectura del disco sería dominante (como debería ser) y las mejoras adicionales casi no tendrían ningún efecto.

Así que quiero enfatizarles nuevamente a ambos: este es un problema real de desempeño.

jreback, dado que la estrategia de concatenación no funciona para categóricos, no crea que las mejoras que sugirió anteriormente funcionarán. Creo que un mejor punto de partida sería volver a indexar. El problema en este momento es que reindex hace muchas cosas adicionales. Pero en principio, un DataFrame con cero filas tiene toda la información necesaria para permitir la creación de un DataFrame con el número correcto de filas, sin hacer ningún trabajo innecesario. Por cierto, esto me hace sentir que los pandas necesitan un objeto de esquema, pero esa es una discusión para otro día.

Creo que tendremos que aceptar estar en desacuerdo. Los DataFrames de la OMI no son objetos de rendimiento extremo en el ecosistema numérico, como lo muestra el orden de la diferencia de magnitud entre una matriz numérica básica y una creación de 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

En este momento, la copia adicional que necesito hacer para crear el DataFrame está tomando aproximadamente la mitad del tiempo en mi programa simple que solo lee los datos fuera del disco y en un DataFrame. Si tomara 1/20 del tiempo, entonces la lectura del disco sería dominante (como debería ser) y las mejoras adicionales casi no tendrían ningún efecto.

Creo que esta es una razón aún menor para preocuparse por el rendimiento de DataFrame: incluso si puede hacerlo 100% gratis, el tiempo total del programa solo disminuye en un 50%.

Estoy de acuerdo en que existe la posibilidad de que haga un PR aquí para resolver este problema, ya sea que desee considerarlo como un problema de rendimiento o como un problema de conveniencia. Desde mi punto de vista, lo veo como lo último, ya que siempre usaré una matriz numpy cuando me importe el rendimiento. Numpy hace otras cosas como no usar un administrador de bloques que es relativamente eficiente para algunas cosas (como hacer crecer la matriz agregando columnas). pero malo desde otros puntos de vista.

Podría haber dos opciones. El primero, un constructor vacío como en el ejemplo que di arriba. Esto no copiaría nada, pero probablemente Null-fill para ser consistente con otras cosas en pandas. El llenado nulo es bastante barato y, en mi opinión, no es la raíz del problema.

El otro sería tener un método DataFrame.from_blocks que tomaría bloques preformados para pasar directamente al administrador de bloques. 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))

Un método de este tipo obligaría a que los bloques tuvieran una forma compatible, que todos los bloques tuvieran tipos únicos, así como las comprobaciones habituales de la forma del índice y las columnas. Este tipo de método no afectaría los datos y lo usaría en BlockManger.

@quicknir, estás tratando de combinar cosas bastante complicadas. Los categóricos no existen en numpy, sino que son un tipo d compuesto como si fuera una construcción de pandas. Debe construir y asignar luego por separado (lo que en realidad es bastante barato; estos no se combinan en bloques como otros dtypes singulares).

@bashtage soln parece razonable. Esto podría proporcionar algunas comprobaciones simples y simplemente pasar los datos (y ser llamado por otras rutinas internas). Normalmente, el usuario no necesita preocuparse por la repr. Interna. Como realmente lo desea, debe ser consciente de esto.

Dicho todo esto, todavía no estoy seguro de por qué no crea un marco exactamente como lo desea. Luego tome los punteros de bloque y cambie los valores. Cuesta la misma memoria, y como @bashtage señala, es bastante barato crear esencialmente un marco nulo (que tiene todos los tipos, índices y columnas) ya configurados.

No estoy seguro de lo que quiere decir con el constructor vacío, pero si se refiere a construir un marco de datos sin filas y el esquema deseado y llamar a reindex, esta es la misma cantidad de tiempo que crear con copy = True.

Su segunda propuesta es razonable, pero solo si puede descubrir cómo hacer categóricas. Sobre ese tema, estaba revisando el código y me di cuenta de que los categóricos no son consolidables. Entonces, en una corazonada, creé una matriz de enteros y dos Series categóricas, luego creé tres DataFrames y concatené los tres. Efectivamente, no realizó una copia a pesar de que dos de los DataFrames tenían el mismo dtype. Intentaré ver cómo hacer que esto funcione para Datetime Index.

@jreback Todavía no sigo lo que quieres decir con crear el marco exactamente como quieres.

@quicknir, ¿por qué no muestra una muestra de código / pseudocódigo de lo que realmente está tratando de hacer?

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

El código anterior estaba construyendo un diccionario de matrices numpy primero, y luego construyendo un DataFrame a partir de eso porque estaba copiando todo. Aproximadamente la mitad del tiempo se dedicaba a eso. Entonces estoy tratando de cambiarlo a este esquema. La cuestión es que construir df como se indicó anteriormente, incluso cuando no te preocupas por el contenido, es extremadamente costoso.

@quicknir dict de matrices np requiere muchas copias.

Simplemente debes hacer esto:

# 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(...)

si haces esto por dtype será barato

entonces.

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

ya que estos valores de bloque son vistas en matrices numpy

La composición de los tipos no se conoce de antemano en general, y en el caso de uso más común hay una combinación saludable de flotantes y entres. Supongo que no entiendo cómo esto será barato, si tengo 30 columnas flotantes y 10 columnas int, entonces sí, los flotadores serán muy baratos. Pero cuando hace los ints, a menos que haya alguna forma de hacerlos todos a la vez que me falta, cada vez que agregue una columna más de ints, se reasignará todo el bloque int.

La solución que me diste anteriormente está cerca de funcionar, parece que no puedo hacer que funcione para DatetimeIndex.

No estoy seguro de lo que quiere decir con el constructor vacío, pero si se refiere a construir un marco de datos sin filas y el esquema deseado y llamar a reindex, esta es la misma cantidad de tiempo que crear con copy = True.

Un constructor vacío se vería así

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

Esto produciría el mismo resultado 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))

solo que no copiaría datos.

Básicamente, el constructor permitiría un dtype mixto para la siguiente llamada que funciona pero solo un dtype básico.

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

La única otra _característica_ sería evitar que llene nulos los arreglos int, lo que tiene el efecto secundario de convertirlos en objeto dtype ya que no falta ningún valor para los ints.

Su segunda propuesta es razonable, pero solo si puede descubrir cómo hacer categóricas. Sobre ese tema, estaba revisando el código y me di cuenta de que los categóricos no son consolidables. Entonces, en una corazonada, creé una matriz de enteros y dos Series categóricas, luego creé tres DataFrames y concatené los tres. Efectivamente, no realizó una copia a pesar de que dos de los DataFrames tenían el mismo dtype. Intentaré ver cómo hacer que esto funcione para Datetime Index.

El método from_block tendría que conocer las reglas de consolidación, por lo que permitiría múltiples categorías, pero solo una de los otros tipos básicos.

sí ... esto no es tan difícil de hacer .... buscando a alguien que quiera tener una suave introducción a los aspectos internos ..... pista. pista. pista .... :)

Jaja, estoy dispuesto a hacer un trabajo de implementación, no me malinterpretes. Intentaré ver los aspectos internos este fin de semana y tener una idea de qué constructor es más fácil de implementar. Primero, aunque necesito lidiar con algunos problemas de DatetimeIndex que tengo en un hilo separado.

@quicknir ¿Has encontrado una solución a esto?

Estoy buscando una forma de asignar de forma económica (pero no llenar) un marco de datos de tipo d mixto para permitir el llenado sin copia de las columnas de una biblioteca cython.

Sería genial si estuviera dispuesto a compartir cualquier código que tenga (incluso semi-funcional) para ayudarme a comenzar.

¿Sería el siguiente un enfoque sensato? Dejé de recrear la lógica de bloqueo trabajando desde un prototipo de marco de datos.

¿Qué tipos necesitan un tratamiento especial además de los categóricos?

Por supuesto, usar el marco de datos creado no es seguro hasta que se haya llenado ...

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 no estoy seguro de cuál es el objetivo final
¿Puedes dar un ejemplo simple?

más concat con copy = False generalmente evitará esto

@jreback Quiero usar una biblioteca cython para leer datos de gran volumen columna por columna desde un almacén de datos comprimido que quiero descomprimir directamente en un marco de datos sin copia intermedia por razones de rendimiento.

Tomando prestado de la solución numpy habitual en tales casos, quiero preasignar la memoria para un marco de datos para poder pasar punteros a estas regiones de memoria asignadas a mi biblioteca cython que luego puede usar c-pointers / c-arrays ordinarios correspondientes a esas regiones de memoria para llenar el marco de datos directamente sin pasos de copia intermedios (o la generación de objetos Python intermedios). La opción de llenar los marcos de datos con múltiples subprocesos de cython en paralelo con gil liberado sería un beneficio adicional.

En pseudocódigo (simplificado), el idom sería 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)

¿Esto tiene algún sentido para tí?

Según tengo entendido, concat con copy=False no fusionará columnas con dtypes idénticos en bloques, pero las operaciones en la línea desencadenarán esto, lo que dará como resultado la copia que estoy tratando de evitar. ¿O entendí mal el funcionamiento interno de los pandas?

Si bien he progresado un poco con la creación de instancias de marcos de datos grandes (no rellenos) (factor ~ 6,7), todavía estoy lejos de las velocidades numpy. Solo queda otro factor de ~ 90 ...

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

Otra esperanza era que este enfoque también podría permitir una instanciación repetida más rápida de DataFrames de forma idéntica cuando los datos de pequeño volumen se leen con frecuencia. Ese no ha sido el objetivo principal hasta ahora, pero sin darme cuenta logré un mejor progreso con esto: solo un factor de ~ 4.8 para ir a la velocidad numpy.

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

Los tiempos anteriores están pintando una imagen demasiado pesimista al comparar manzanas con naranjas: mientras que la columna de cadena numpy se crea como cadenas nativas de longitud fija, la columna equivalente en pandas se creará como una matriz de objetos de Python. La comparación entre iguales empuja la creación de instancias de DataFrame a velocidades numerosas, con la excepción de la generación de índices, que es responsable de aproximadamente el 92% del tiempo de creación de instancias.

@ ARF1 si quieres velocidades numpy, usa numpy. No estoy seguro de qué está haciendo realmente o qué está haciendo en cython. Las soluciones habituales son fragmentar sus cálculos, pasar tipos d individuales a cython o simplemente obtener una máquina más grande.

Los DataFrames hacen mucho más que numpy sobre cómo describen y manipulan los datos. No es lo que realmente estás haciendo con ellos.

casi todas las operaciones de pandas copian. (al igual que la mayoría de las operaciones numpy), así que no estoy seguro de lo que está buscando.

@jreback Actualmente estoy usando numpy pero tengo dtypes mixtos que solo (convenientemente) pueden manejarse con matrices estructuradas. Sin embargo, las matrices estructuradas están inherentemente ordenadas por filas, lo que choca con mi dimensión de análisis típica y conduce a un rendimiento deficiente. Pandas parece la alternativa natural debido a su ordenación de columnas principales, si puedo obtener los datos en el marco de datos a una buena velocidad.

Por supuesto, la alternativa sería usar un dictado de matrices numpy de tipo d diferente, pero eso hace que el análisis sea un dolor ya que cortar, etc. ya no es posible.

Las soluciones habituales son fragmentar sus cálculos, pasar tipos d individuales a cython.

Eso es lo que estoy haciendo con la variable block_arrays en mi ejemplo.

o simplemente consigue una máquina más grande.

Un factor de 100+ más rápido es un desafío financiero para mí. ;-)

@ ARF1 tienes un modelo muy extraño de cómo funcionan las cosas. Por lo general, crea una pequeña cantidad de marcos de datos y luego trabaja en ellos. La velocidad de creación es una pequeña fracción de cualquier cálculo o manipulación real.

@jreback : este no es un modelo extraño. Tal vez sea un modelo extraño si ves las cosas desde una perspectiva pura de Python. Si está trabajando con código C ++, la forma más fácil de leer datos en objetos Python es pasarle punteros a objetos Python preexistentes. Si está haciendo esto en un contexto sensible al rendimiento, desea una forma económica y estable (en el sentido de la ubicación de la memoria) para crear el objeto Python.

Honestamente, no estoy seguro de por qué esta actitud es común en los foros de pandas. Creo que es desafortunado, en la medida en que entiendo que los pandas son una construcción de mayor nivel que numpy, aún podría ser más fácil para las personas desarrollarse "encima" de los pandas. El DataFrame de pandas es, con mucho, el tipo más deseable para trabajar si tiene un código C que quiere escupir datos tabulares en Python, por lo que esto realmente parece un caso de uso importante.

Por favor, no tome lo que estoy escribiendo negativamente, si no pensara que pandas DataFrames es tan increíble, simplemente usaría registros numpy o algo así y terminaría con eso.

@ ARF1 : En última instancia, no recuerdo las razones, pero lo mejor que pude hacer fue crear un DataFrame para cada tipo numérico a partir de una matriz numpy con Copy = False, y luego usar pandas.concat con Copy = False nuevamente para concatenarlos. Cuando crea un DataFrame de un solo tipo a partir de una matriz numpy, tenga mucho cuidado con la orientación de la matriz numpy. Si la orientación es incorrecta, entonces las matrices numéricas correspondientes a cada columna serán escalonadas de manera no trivial, y a los pandas no les gusta esto y harán una copia en la primera oportunidad. Puede agregar los categóricos al final, ya que no se consolidan y no deberían activar ninguna copia del resto del marco.

Recomiendo escribir algunas pruebas unitarias que realicen esta operación paso a paso y tomar continuamente los punteros a los datos subyacentes (a través de la interfaz de

@quicknir si tú lo dices. Creo que debería simplemente crear un perfil antes de intentar optimizar las cosas. Como dije antes, y probablemente lo volveré a hacer. El tiempo de construcción no debe dominar nada. Si lo hace, entonces solo está usando el DataFrame para guardar cosas, entonces, ¿cuál es el punto de usarlo en primer lugar? Si no domina, ¿cuál es el problema?

@jreback Escribes eso, asumiendo que aún no he perfilado. De hecho, tengo. Tenemos código c ++ y python que ambos deserializan datos tabulares del mismo formato de datos. Si bien esperaba que el código de Python tuviera un poco de sobrecarga, pensé que la diferencia debería ser pequeña, porque el tiempo de lectura del disco debería dominar. Este no era el caso, antes de irme y reelaborar las cosas con mucho cuidado para minimizar las copias, la versión de Python tardaba el doble o peor en comparación con el código C ++, y casi toda la sobrecarga estaba solo en la creación del DataFrame. En otras palabras, tomó aproximadamente tanto tiempo simplemente crear un DataFrame de cierto tamaño muy grande cuyo contenido no me importaba en absoluto, como leer, descomprimir y escribir los datos que me importaban en ese DataFrame. Eso es un rendimiento extremadamente pobre.

Si yo fuera un usuario final de este código con operaciones específicas en mente, tal vez lo que está diciendo acerca de que la construcción no domina sería válido. En realidad, soy un desarrollador y los usuarios finales de este código son otras personas. No sé exactamente qué harán con el DataFrame, el DataFrame es la única forma de obtener una representación en memoria de los datos en el disco. Si quieren hacer algo muy simple con los datos en el disco, todavía tienen que pasar por el formato DataFrame.

Obviamente, podría admitir más formas de obtener los datos (por ejemplo, construcciones numpy), pero esto aumentaría enormemente la ramificación en el código y haría las cosas mucho más difíciles para mí como desarrollador. Si hubiera alguna razón fundamental por la que los DataFrames deben ser tan lentos, lo entendería y decidiría si admitir DataFrame, numpy o ambos. Pero no hay una razón real por la que deba ser tan lento. Se podría escribir un método DataFrame.empty que toma una matriz de tuplas donde cada tupla contiene el nombre y el tipo de columna, y el número de filas.

Esta es la diferencia a la que me refiero entre los usuarios de apoyo y los redactores de bibliotecas. Es más fácil escribir su propio código que escribir una biblioteca. Y es más fácil hacer que su biblioteca solo admita usuarios en lugar de otros escritores de bibliotecas. Solo creo que en este caso, la asignación vacía de DataFrames sería una fruta madura en los pandas que facilitaría la vida de personas como yo y @ ARF1 .

bueno, si desea tener una solución documentada probada razonable, todos los oídos. pandas tiene bastantes usuarios / desarrolladores. Esa es la razón por la que el DataFrame es tan versátil y la misma razón por la que necesita mucha verificación e inferencia de errores. Le invitamos a ver lo que puede hacer como se describe anteriormente.

Estoy dispuesto a dedicar algo de tiempo a implementar esto, pero solo si hay un consenso razonable sobre el diseño de algunos de los desarrolladores de pandas. Si envío una solicitud de extracción y hay ciertas cosas que la gente quiere cambiar, está bien. O si me doy cuenta, después de dedicarle diez horas, que no hay forma de hacer algo limpiamente, y que la única forma de hacerlo podría involucrar algo que la gente piensa que es objetable, eso también es genial. Pero no estoy realmente de acuerdo con pasar X horas y que me digan que esto no es tan útil, la implementación es desordenada, no creemos que realmente se pueda limpiar, complica el código base, etc. No sé si Estoy muy equivocado con este sentimiento, no he hecho contribuciones importantes a un proyecto de OSS antes, así que no sé cómo funciona. Es solo que en mi publicación inicial comencé proponiendo esto mismo, y luego, francamente, me dio la impresión de que estaba "fuera de alcance" para los pandas.

Si lo desea, puedo abrir un nuevo número, crear una propuesta de diseño tan específica como pueda, y una vez que haya comentarios / aprobación tentativa, trabajaré en ello cuando pueda.

@quicknir, la clave es que debe pasar todo el conjunto de pruebas, que es bastante completo.

Esto no está fuera del alcance de pandas, pero la API debe ser algo fácil de usar.

No estoy seguro de por qué no te gustó

concat(list_of_arrays,axis=1,copy=False) Creo que esto hace exactamente lo que quieres (y si no, no aclara lo que realmente quieres).

Terminé usando una técnica similar, pero con una lista de DataFrames que se crearon a partir de una única matriz numpy, cada una de diferentes tipos.

En primer lugar, creo que todavía encontré algunas copias cuando hice esta técnica. Como dije, los pandas no siempre respetan copy = False, por lo que es muy agotador ver si su código realmente se está copiando o no. Realmente deseo que para pandas 17, los desarrolladores consideren hacer copy = True como predeterminado, y luego copy = False se lanza cuando una copia no se puede elidir. Pero de todos modos.

En segundo lugar, otro problema fue tener que reordenar las columnas posteriormente. Esto fue sorprendentemente incómodo, la única forma que pude encontrar para hacer esto sin que se hiciera una copia fue originalmente hacer que los nombres de las columnas fueran enteros que estaban ordenados en el orden final deseado. Luego hice una clasificación de índice en su lugar. Luego cambié los nombres de las columnas.

En tercer lugar, descubrí que las copias eran inevitables para los tipos de marca de tiempo (numpy datetime64).

Escribí este código hace un tiempo, por lo que no está fresco en mi mente. Es posible que cometiera errores, pero lo revisé con mucho cuidado y esos fueron los resultados que obtuve en ese momento.

El código que proporcionó anteriormente ni siquiera funciona para matrices numpy. Falla con: TypeError: no se puede concatenar un objeto que no sea NDFrame. Primero tienes que hacerlos DataFrames.

No es que no me guste la solución que me has dado aquí o arriba. Todavía tengo que ver uno simple que funcione.

@quicknir bueno, mi ejemplo anterior funciona. Por favor, proporcione exactamente lo que está haciendo y puedo intentar ayudarlo.

pd.concat ([np.zeros ((2,2))], eje = 1, copia = Falso)

Estoy en pandas 0.15.2, así que ¿quizás esto comenzó a funcionar en 0.16?

Por favor, lea la cadena de documentos de pd.concat . necesitas pasar un DataFrame

por cierto copy=True ES el valor predeterminado

Bien, eso es lo que escribí. El fragmento de código que escribió anteriormente tenía list_of_arrays, no list_of_dataframes. De todos modos, creo que nos entendemos. Terminé usando el método pd.concat, pero no es bastante trivial, hay un montón de trampas para hacer tropezar a la gente:

1) Debe crear una lista de DataFrames. Cada DataFrame debe tener exactamente un dtype distinto. Por lo tanto, debe recopilar todos los tipos diferentes antes de comenzar.

2) Cada DataFrame debe crearse a partir de una única matriz numérica del tipo d deseado, el mismo número de filas, el número deseado de columnas y el indicador order = 'F'; if order = 'C' (predeterminado), los pandas a menudo harán copias cuando de otra manera no lo harían.

3) Ignorar 1) para los categóricos, no se amalgaman en un bloque para que pueda agregarlos más tarde.

4) Cuando crea todos los DataFrames individuales, las columnas deben nombrarse utilizando números enteros que representen el orden en el que los desea. De lo contrario, es posible que no haya forma de cambiar el orden de las columnas sin activar copias.

5) Después de haber creado su lista de DataFrames, use concat. Tendrá que verificar minuciosamente que no estropeó nada, porque copy = False no se arrojará si una copia no se puede elidir, sino que se copiará silenciosamente.

6) Ordene el índice de la columna para lograr el orden que desee, luego cambie el nombre de las columnas.

Apliqué este procedimiento rigurosamente. No es una línea única, hay muchos lugares para cometer errores, estoy bastante seguro de que todavía no funcionó para las marcas de tiempo y hay una gran cantidad de gastos generales innecesarios que podrían eludirse al no usar solo la interfaz. Si lo desea, puedo escribir un borrador de cómo se ve esta función usando solo la API pública, tal vez combinada con algunas pruebas para ver si realmente elimina las copias y para qué dtypes.

Además, copy = False es el valor predeterminado para, por ejemplo, el constructor DataFrame. Mi punto principal es más que una función que no puede respetar sus argumentos debería lanzar en lugar de "hacer algo razonable". Es decir, si no se puede aceptar copy = False, se debe lanzar una excepción para que el usuario sepa que tiene que cambiar otras entradas para que la elisión de la copia pueda tener lugar, o tiene que cambiar la copia a True. Una copia nunca debería ocurrir en silencio cuando copy = False, esto es más sorprendente y menos propicio para que un usuario consciente del rendimiento encuentre errores.

tienes muchos pasos aquí que no son necesarios
Por favor, muestre un ejemplo real como lo hice anteriormente.

comprende que una vista numerosa puede devolver una copia mediante operaciones de remodelación muy simples (a veces) y no otras

Solo habrá una garantía blanda en la copia, ya que generalmente no es posible sin mucha introspección para garantizar esto, lo que, por definición, frustra el propósito de un código de desempeño simple y poderoso.

El comportamiento de copy=False en la construcción del DataFrame es consistente con la función np.array numpy (por ejemplo, si proporciona una lista de matrices, los datos finales siempre harán una copia).

Parece que esta es una brecha de características desafortunada en los pandas. En mi humilde opinión, nunca vamos a tener una solución satisfactoria con el modelo actual para pandas internos (que consolida bloques). Desafortunadamente, esa no es realmente una opción para los pandas, porque los pandas aún necesitan funcionar para las personas que crean DataFrames con muchas columnas.

Lo que necesitamos es una implementación de DataFrame alternativa diseñada específicamente para trabajar con datos ordenados que almacene cada columna de forma independiente como matrices 1D numpy. En realidad, esto es algo similar al modelo de datos en Xray , excepto que permitimos que las columnas sean matrices N-dimensionales.

Creo que un constructor general de marcos de datos de pandas de alto rendimiento que solo asigna espacio no es trivial dada la amplia gama de diferentes tipos de columnas que necesitaría admitir.

Dicho esto, parece bastante sencillo para los escritores de bibliotecas que desean usar marcos de datos de pandas como contenedor de datos de alto rendimiento para implementar un constructor de marcos de datos de solo asignación limitado a los tipos de columna que requieren.

El siguiente segmento de código puede servir de inspiración. Permite la creación de instancias de tramas de datos no rellenas de solo asignación a velocidades cercanas a numerosas. Tenga en cuenta que el código requiere 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)

Con el constructor de ejemplo allocate_like() la penalización de rendimiento cf numpy es solo x2.3 (normalmente x333) para matrices grandes y x3.3 (normalmente x8.9) para matrices de tamaño cero:

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

Lo siento, perdí la pista de esto por un tiempo. @ ARF1 ,

Realmente siento que crear una clase que se corresponda con el diseño del DataFrame, sin ninguno de los datos, hará que el código como el anterior sea mucho más natural y quizás también más eficaz. Esta clase también podría reutilizarse, por ejemplo, al realizar la reindexación de las filas.

Lo que propongo básicamente es algo como esto: una clase llamada DataFrameLayout, que envuelve los dtypes, los nombres de las columnas y el orden de las columnas. Por ejemplo, podría almacenar un dict desde dtype a números de columna (para ordenar) y una matriz separada con todos los nombres. Desde este diseño, puede ver que una iteración simple y elegante sobre el dictado permitiría crear los administradores de bloques rápidamente. Esta clase podría usarse en lugares como un constructor vacío o en operaciones de reindexación.

Creo que estas abstracciones son necesarias para datos más complejos. En cierto sentido, DataFrame es un tipo de datos compuestos y un DataFrameLayout especificaría la naturaleza exacta de la composición.

Por cierto, creo que algo similar es necesario para los categóricos; es decir, debe haber una abstracción CategoricalType que almacene las categorías, estén o no ordenadas, el tipo de matriz de respaldo, etc. Es decir, todo menos los datos reales. De hecho, cuando piensa en un DataFrameLayout, se da cuenta de que todas las columnas deben tener tipos completamente especificados, y eso actualmente es problemático para los categóricos.

¿Qué piensa la gente de estas dos clases?

@quicknir Ya tenemos una clase CategoricalDtype . Estoy de acuerdo en que podría extenderse hasta el CategoricalType completo que describe.

No estoy del todo seguro acerca de la clase DataFrameLayout . Básicamente, creo que podríamos usar un modelo de datos alternativo y más simple para marcos de datos (más similar a cómo se hacen en R o Julia). Hay cierto interés en este tipo de cosas y sospecho que eventualmente sucederá de alguna forma, pero probablemente no pronto (y quizás nunca como parte del proyecto pandas).

@quicknir yeh, DataFrameLayout está reinventando la rueda aquí. Ya tenemos una especificación dtype, p. Ej.

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

@jreback No está reinventando la rueda, ya que la especificación dtype tiene varios problemas importantes:

1) Por lo que puedo ver, to_records () realizará una copia profunda de todo el DataFrame. Obtener la especificación (solo usaré este término de ahora en adelante) para el DataFrame debería ser barato y fácil.

2) La salida de to_records es de tipo numpy. Una implicación de esto es que no veo cómo esto podría extenderse para admitir adecuadamente los categóricos.

3) Este método de almacenar internamente la especificación no es fácilmente compatible con la forma en que se almacenan los datos dentro del DataFrame (es decir, en bloques de tipo d similar). La creación de los bloques a partir de dicha especificación implica mucho trabajo adicional que podría eludirse almacenando la especificación de la manera que sugerí, con un dictado de dtype a los números de columna. Cuando tiene un DataFrame con 2000 columnas, esto será costoso.

En resumen, el tipo d de la representación de registro es más una solución para la falta de una especificación adecuada. Carece de varias características clave y su rendimiento es mucho más pobre.

Hay muchos hilos en SO que solicitan esta función.

Me parece que todos estos problemas provienen de BlockManager que consolida columnas separadas en un solo fragmento de memoria (los 'bloques').
¿No sería la solución más fácil no consolidar los datos en bloques cuando se especifica copy = False?

Tengo un BlockManager parcheado con mono que no se consolida:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
que solía solucionar este problema.

¿Fue útil esta página
0 / 5 - 0 calificaciones