Pandas: Невозможно построить DataFrame смешанного типа без полной копии, предлагаемое решение

Созданный на 9 янв. 2015  ·  58Комментарии  ·  Источник: pandas-dev/pandas

После нескольких часов рвоты я пришел к выводу, что невозможно создать DataFrame смешанного типа dtype без копирования всех его данных. То есть, что бы вы ни делали, если вы хотите создать DataFrame смешанного типа dtype. , вы неизбежно создадите временную версию данных (например, используя np.empty), и различные конструкторы DataFrame всегда будут делать копии этих временных. Этот вопрос уже поднимался год назад: https://github.com/pydata/pandas/issues/5902.

Это особенно ужасно для взаимодействия с другими языками программирования. Если вы планируете заполнить данные в DataFrame, например, из вызова C, самый простой способ сделать это, безусловно, - создать DataFrame в python, получить указатели на базовые данные, которые являются np.arrays, и передать эти np .arrays, чтобы их можно было заполнить. В этой ситуации вам просто все равно, с каких данных начинается DataFrame, цель - просто выделить память, чтобы вы знали, что копируете.

Это также обычно расстраивает, поскольку подразумевает, что в принципе (в зависимости от конкретной ситуации, особенностей реализации и т. Д.) Трудно гарантировать, что вы не будете использовать вдвое больше памяти, чем действительно следовало бы.

У этого есть чрезвычайно простое решение, которое уже основано на количественном стеке Python: иметь метод, аналогичный пустому numpy. Это выделяет пространство, но фактически не тратит время на написание или копирование чего-либо. Поскольку пустой уже занят, я предлагаю вызвать метод from_empty. Он будет принимать индекс (обязательный, наиболее распространенный вариант использования - передача np.arange (N)), столбцы (обязательно, обычно список строк), типы (список допустимых типов для столбцов, той же длины, что и столбцы). Список типов должен включать поддержку всех числовых типов numpy (int, float), а также специальных столбцов Pandas, таких как DatetimeIndex и Categories.

В качестве дополнительного бонуса, поскольку реализация находится в совершенно отдельном методе, она вообще не будет мешать существующему API.

API Design Constructors Dtypes

Самый полезный комментарий

На SO есть много потоков, которые просят эту функцию.

Мне кажется, что все эти проблемы связаны с тем, что BlockManager объединяет отдельные столбцы в единые блоки памяти («блоки»).
Не было бы самым простым решением не объединять данные в блоки, если указано copy = False.

У меня есть неконсолидирующийся BlockManager с исправлением обезьяны:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
что я использовал, чтобы обойти эту проблему.

Все 58 Комментарий

вы можете просто создать пустой фрейм с индексом и столбцами
затем назначьте ndarrays - они не будут копировать вас, назначив сразу весь конкретный тип dtype

вы можете создать их с помощью np.empty, если хотите

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

Похоже, он копирует меня. Если только код, который я написал, не то, что вы имели в виду, или произошедшее копирование не является копией, которую, как вы думали, я пытался исключить.

вы изменили dtype
вот почему он скопировал попытку с поплавком

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

Тип dtype - это объект, поэтому он изменяется независимо от того, использую ли я float или 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

Создание без копии на смешанном шрифте может быть выполнено, но это довольно сложно. Проблема в том, что для некоторых типов требуется копия (например, объект, чтобы избежать проблем с памятью). А внутренняя структура объединяет различные типы, поэтому для добавления нового типа потребуется копия. В большинстве случаев избежать копирования довольно сложно.

Вам нужно просто создать то, что вам нужно, получить указатели на данные, а затем перезаписать их. Почему это проблема?

Проблема в том, что для создания того, что мне нужно, я должен скопировать материал правильного типа dtype, данные которого я не собираюсь использовать. Даже если предположить, что ваше предложение о создании пустого DataFrame не использует значительного объема оперативной памяти, это не снижает затрат на копирование. Если я хочу создать 1-гигабайтный DataFrame и заполнить его где-нибудь еще, мне придется заплатить стоимость копирования гигабайта мусора в памяти, что совершенно ненужно. Вы не видите в этом проблемы?

Да, я понимаю, что внутренняя структура объединяет разные типы. Я не совсем понимаю, что вы имеете в виду под проблемами нехватки памяти, но в любом случае объекты здесь не представляют интереса.

На самом деле, хотя избегать копий в целом - сложная проблема, избежать их тем способом, который я предлагал, довольно легко, потому что я предоставляю всю необходимую информацию с самого начала. Это идентично построению из данных, за исключением того, что вместо вывода dtypes и # строк из данных и копирования данных вы напрямую указываете dtypes и # строк, а все остальное делаете точно так же, как вы бы сделали без копии.

Вам нужен «пустой» конструктор для каждого поддерживаемого типа столбца. Для numpy числовых типов это очевидно, требуется ненулевое значение для Категориального, а насчет DatetimeIndex нет уверенности.

передача словаря конструктору и copy = False должен работать

Так что это сработает. Но вы должны быть УВЕРЕНЫ, что массивы, которые вы передаете, являются разными типами. И как только вы что-нибудь с этим сделаете, он сможет скопировать базовые данные. Итак, YMMV. вы, конечно, можете передать np.empty вместо единиц / нулей, как я.

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)

_ Первоначальная попытка удалена, поскольку не работает, поскольку reindex принудительно выполняет приведение, что является странной "особенностью" ._

Придется использовать метод, который делает эту попытку менее удовлетворительной:

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

Если вы действительно беспокоитесь о производительности, я не уверен, почему бы просто не использовать numpy как можно больше, поскольку это концептуально намного проще.

jreback, спасибо за ваше решение. Кажется, это работает даже для категорий (что меня удивило). Если у меня возникнут проблемы, я дам вам знать. Я не уверен, что вы имеете в виду: если вы сделаете что-нибудь с этим, это может скопировать. Что вы имеете в виду под чем-нибудь? Если нет семантики COW, я бы подумал, что то, что вы видите, - это то, что вы получаете в отношении глубоких и мелких копий во время создания.

Я все еще думаю, что конструктор from_empty должен быть реализован, и я не думаю, что это было бы так сложно, хотя этот метод работает, он требует больших накладных расходов на код. В принципе это можно сделать, указав один составной dtype и несколько строк.

bashtage, эти решения по-прежнему записываются во весь DataFrame. Поскольку запись обычно медленнее, чем чтение, это означает, что в лучшем случае она экономит менее половины рассматриваемых накладных расходов.

Очевидно, что если я не использовал numpy, то это потому, что у pandas есть много замечательных функций и возможностей, которые мне нравятся, и я не хочу отказываться от них. Вы действительно спрашивали или просто подразумевали, что мне следует использовать numpy, если я не хочу снижать производительность?

Поцарапайте, пожалуйста, ошибку пользователя, и мои извинения. reindex_axis с copy = False работал отлично.

bashtage, эти решения по-прежнему записываются во весь DataFrame. Поскольку запись обычно медленнее, чем чтение, это означает, что в лучшем случае она экономит менее половины рассматриваемых накладных расходов.

Верно, но все, что вам нужно, для нового method для reindex , который ничем не заполнится, а затем вы можете выделить типизированный массив с произвольными типами столбцов без записи / копирования.

Очевидно, что если я не использовал numpy, то это потому, что у pandas есть много замечательных функций и возможностей, которые мне нравятся, и я не хочу отказываться от них. Вы действительно спрашивали или просто подразумевали, что мне следует использовать numpy, если я не хочу снижать производительность?

Это было немного риторическим, хотя также серьезным предложением с точки зрения производительности, поскольку numpy значительно упрощает доступ к доступу к данным в виде большого двоичного объекта памяти, что важно, если вы пытаетесь писать очень код высокой производительности. Вы всегда можете преобразовать numpy в pandas, когда простота кода важнее производительности.

Я понимаю, о чем вы говорите. Я по-прежнему считаю, что это должно быть более чисто частью интерфейса, а не обходным путем, но, поскольку обходные пути идут, это хороший способ, который легко реализовать.

Pandas по-прежнему подчеркивает производительность как одну из своих основных целей. Очевидно, что у него есть функции более высокого уровня по сравнению с numpy, и за них нужно платить. То, о чем мы говорим, не имеет ничего общего с этими функциями более высокого уровня, и нет причин, по которым нужно платить за массовые копии там, где они вам не нужны. Ваше предложение было бы уместным, если бы кто-то думал о стоимости настройки столбцов, индекса и т. Д., Что полностью отличается от этого обсуждения.

Я думаю, вы переоцениваете стоимость написания по сравнению с кодом распределения памяти в Python - дорогостоящая часть - это выделение памяти. Создание объекта тоже дорогое удовольствие.

Оба выделяют 1 ГБ памяти, один пустой и один ноль.

%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 мкс для обнуления 150 000 000 значений.

Теперь сравните их для тривиального DataFrame.

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

Примерно в 200 раз медленнее для тривиального. Но для массивов большего размера все намного хуже.

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

Теперь она занимает 275 м ы - к сведению , что это ничего не копируя. Стоимость заключается в настройке индекса и т. Д., Что явно очень медленно, когда массив нетривиально большой.

Для меня это похоже на преждевременную оптимизацию, поскольку другие накладные расходы в pandas настолько велики, что компонент malloc + filliing почти равен нулю.

Кажется, что если вы хотите выделить что-либо в жестком цикле, это должен быть массив numpy по соображениям производительности.

хорошо, вот что, я думаю, нам следует сделать, @quicknir, если вы хотите внести некоторые улучшения. 2 выпуска.

  • # 4464 - это, по сути, позволяет использовать составной dtype в конструкторе DataFrame а затем развернуться и вызвать from_records() , который также может быть вызван, если переданный в массиве является rec / структурированным массивом - это в основном сделает from_records путем обработки массива rec / struct
  • пройти через ключевое слово copy= в from_records
  • from_records может затем использовать concat soln, который я показываю выше, вместо того, чтобы разделять массив rec-array, дезинфицировать их (как серии) и затем снова объединять (в блоки dtype; эта часть делается внутри).

Это немного нетривиально, но тогда можно будет довольно легко передать уже созданный ndarray (может быть пустым) со смешанными типами. Обратите внимание, что это скорее всего (в реализации первого прохода) будет обрабатывать только (int / float / string). поскольку datetime / timedelta нуждаются в специальной дезинфекции и немного усложняют это.

так что @bashtage прав с точки зрения производительности. Имеет смысл просто построить фрейм так, как вы хотите, а затем изменить ndarrays (но вы ДОЛЖНЫ делать это, захватывая блоки, иначе вы получите копии).

Выше я имел в виду следующее. Pandas группирует любой like-dtype (например, int64, int32 разные) в «блок» (2-d в кадре). Это непрерывный ndarray памяти (который был недавно выделен, если он просто не передается, в котором в настоящее время работает только для одного dtype). Если затем вы выполните setitem, например, df['new_columns'] = 5 и у вас уже есть блок int64, то этот новый столбец будет окончательно объединен с ним (что приведет к новому выделению памяти для этого dtype). Если вы использовали ссылку в качестве представления об этом, она больше не будет действительна. Вот почему эту стратегию нельзя использовать без пиринга на внутреннем устройстве DataFrame.

@bashtage yeh, как вы заметили, большая стоимость - это индекс. RangeIndex (см. # 939) полностью решит эту проблему. (на самом деле это почти сделано в боковой ветке, просто нужно немного стереть пыль).

Даже с оптимизированным RangeIndex он все равно будет на 2 порядка медленнее, чем построение массива NumPy, что достаточно справедливо, учитывая гораздо более тяжелый вес и дополнительные возможности DataFrame .

Я думаю, это можно рассматривать только как удобную функцию, а не как проблему производительности. Было бы полезно инициализировать смешанный тип DataFrame или Panel like.

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])

это только проблема API / удобства

согласился, что производительность действительно случайная проблема (а не драйвер)

@bashtage

% timeit pd.DataFrame (np.empty ((100, 1000000)))
100 циклов, лучшее из 3: 15,6 мс на цикл

% timeit pd.DataFrame (np.empty ((100, 1000000)), copy = True)
1 цикл, лучшее из 3: 302 мс на цикл

Таким образом, копирование в фрейм данных, кажется, занимает в 20 раз больше времени, чем вся другая работа, связанная с созданием фрейма данных, то есть копирование (и дополнительное выделение) выполняется в 95% случаев. Тесты, которые вы выполнили, не дают правильного результата. Независимо от того, занимает ли время сама копия или выделение, на самом деле не имеет значения, дело в том, что если бы я мог избегать копий для нескольких DataFrame dtype так, как я могу для одного dtype DataFrame, я мог бы сэкономить огромное количество времени.

Ваши рассуждения на два порядка тоже обманчивы. Это не единственная выполняемая операция, выполняются и другие операции, которые требуют времени, например, чтение с диска. Прямо сейчас дополнительная копия, которую мне нужно сделать для создания DataFrame, занимает примерно половину времени в моей простой программе, которая просто считывает данные с диска в DataFrame. Если бы на это ушло 1/20 часть времени, то чтение с диска было бы доминирующим (как и должно быть), и дальнейшие улучшения почти не возымели бы никакого эффекта.

Поэтому я хочу еще раз подчеркнуть для вас обоих: это реальная проблема производительности.

jreback, учитывая, что стратегия конкатенации не работает для категорий, не думайте, что предложенные вами выше улучшения будут работать. Я думаю, что лучше начать с переиндексации. Проблема в том, что переиндекс делает много дополнительных вещей. Но в принципе DataFrame с нулевыми строками имеет всю информацию, необходимую для создания DataFrame с правильным количеством строк, без выполнения какой-либо ненужной работы. Кстати, это заставляет меня действительно чувствовать, что пандам нужен объект схемы, но это обсуждение на другой день.

Я думаю, нам придется согласиться, чтобы не согласиться. IMO DataFrames не являются объектами с экстремальной производительностью в числовой экосистеме, как показывает разница в порядке увеличения между базовым массивом numpy и созданием 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

Прямо сейчас дополнительная копия, которую мне нужно сделать для создания DataFrame, занимает примерно половину времени в моей простой программе, которая просто считывает данные с диска в DataFrame. Если бы на это ушло 1/20 часть времени, то чтение с диска было бы доминирующим (как и должно быть), и дальнейшие улучшения почти не возымели бы никакого эффекта.

Я думаю, что это еще меньше поводов для беспокойства о производительности DataFrame - даже если вы можете сделать его на 100% бесплатным, общее время программы сократится только на 50%.

Я согласен с тем, что у вас есть возможность провести здесь PR, чтобы решить эту проблему, независимо от того, хотите ли вы думать об этом как о проблеме производительности или как о проблеме удобства. С моей точки зрения, я вижу это как последнее, так как я всегда буду использовать массив numpy, когда меня волнует производительность. Numpy делает и другие вещи, например, не использует диспетчер блоков, который относительно эффективен для некоторых вещей (например, для увеличения массива путем добавления столбцов). но плохо с других точек зрения.

Возможны два варианта. Первый - пустой конструктор, как в приведенном выше примере. Это не будет ничего копировать, но, вероятно, будет выполнять Null-fill, чтобы соответствовать другим вещам в pandas. Нулевое заполнение довольно дешево и не является корнем проблемы IMO.

Другой - иметь метод DataFrame.from_blocks который будет принимать предварительно сформированные блоки для передачи напрямую диспетчеру блоков. Что-то типа

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

Метод этого типа будет обеспечивать, чтобы блоки имели совместимую форму, все блоки имели уникальные типы, а также обычные проверки формы индекса и столбцов. Этот тип метода ничего не сделает с данными и будет использовать его в BlockManger.

@quicknir, вы пытаетесь совместить довольно сложные вещи. Категориальные не существуют в numpy, скорее они представляют собой составной dtype, подобный конструкции pandas. Вы должны создавать и назначать их отдельно (что на самом деле довольно дешево - они не объединяются в блоки, как другие единичные типы).

@bashtage soln кажется разумным. Это может обеспечить некоторые простые проверки и просто передавать данные (и вызываться другими внутренними подпрограммами). Обычно пользователю не нужно беспокоиться о внутреннем представлении. Поскольку вы действительно этого хотите, вам нужно осознавать это.

С учетом всего сказанного, я все еще не уверен, почему вы просто не создаете рамку именно так, как хотите. Затем возьмите указатели блоков и измените значения. Он стоит столько же памяти, и, как указывает

Не уверен, что вы имеете в виду под пустым конструктором, но если вы имеете в виду создание фрейма данных без строк и желаемой схемы и вызов метода переиндексации, это то же время, что и создание с помощью copy = True.

Ваше второе предложение разумно, но только в том случае, если вы научитесь делать категории. По этому поводу я просматривал код и понял, что категориальные элементы не подлежат консолидации. Итак, догадываясь, я создал целочисленный массив и две категориальные серии, затем я создал три DataFrames и объединил все три. Разумеется, он не выполнял копирование, хотя два DataFrames имели одинаковый dtype. Я постараюсь увидеть, как заставить это работать для Datetime Index.

@jreback Я до сих пор не понимаю, что вы имеете в виду,

@quicknir, почему бы вам не показать образец кода / псевдокода того, что вы на самом деле пытаетесь сделать.

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

В предыдущем коде сначала создавался словарь множественных массивов, а затем из него конструировался DataFrame, потому что он все копировал. На это уходило около половины времени. Поэтому я пытаюсь изменить это на эту схему. Дело в том, что построение df, как указано выше, даже если вас не волнует содержимое, чрезвычайно дорого.

@quicknir dict массивов np требует большого количества копирования.

Вам нужно просто сделать это:

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

если вы сделаете это с помощью dtype, это будет дешево

тогда.

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

поскольку эти значения блока являются представлениями в массивы numpy

Состав типов в целом заранее не известен, и в наиболее распространенном варианте использования существует здоровое сочетание чисел с плавающей запятой и целых чисел. Думаю, я не понимаю, насколько это будет дешево, если у меня есть 30 столбцов с плавающей запятой и 10 столбцов типа int, тогда да, плавающие будут очень дешевыми. Но когда вы выполняете целые числа, если нет способа сделать их все сразу, чего мне не хватает, каждый раз, когда вы добавляете еще один столбец целых чисел, это приведет к перераспределению всего блока int.

Решение, которое вы мне дали ранее, близко к рабочему, я не могу заставить его работать для DatetimeIndex.

Не уверен, что вы имеете в виду под пустым конструктором, но если вы имеете в виду создание фрейма данных без строк и желаемой схемы и вызов метода переиндексации, это то же время, что и создание с помощью copy = True.

Пустой конструктор будет выглядеть как

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

Это даст тот же результат, что и

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

только он не будет копировать данные.

В основном конструктор позволяет использовать смешанный dtype для следующего вызова, который работает, но только с одним базовым dtype.

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

Единственная другая _feature_ - это предотвращение заполнения массивов int нулевым значением, что имеет побочный эффект преобразования их в объект dtype, поскольку для int отсутствует отсутствующее значение.

Ваше второе предложение разумно, но только в том случае, если вы научитесь делать категории. По этому поводу я просматривал код и понял, что категориальные элементы не подлежат консолидации. Итак, догадываясь, я создал целочисленный массив и две категориальные серии, затем я создал три DataFrames и объединил все три. Разумеется, он не выполнял копирование, хотя два DataFrames имели одинаковый dtype. Я постараюсь увидеть, как заставить это работать для Datetime Index.

Метод from_block должен знать правила консолидации, чтобы можно было использовать несколько категорий, но только один из других базовых типов.

да ... это не так уж и сложно .... ищу кого-то, кто хочет нежно познакомиться с внутренним устройством ..... hint.hint.hint .... :)

Ха-ха, я готов поработать над реализацией, поймите меня правильно. В эти выходные я попытаюсь изучить внутреннее устройство и понять, какой конструктор проще реализовать. Хотя сначала мне нужно разобраться с некоторыми проблемами DatetimeIndex, которые у меня возникают в отдельном потоке.

@quicknir Вы нашли решение этой проблемы?

Я ищу способ дешево выделить (но не заполнить) фрейм данных смешанного типа, чтобы разрешить заполнение столбцов из библиотеки cython без копирования.

Было бы здорово, если бы вы были готовы поделиться любым имеющимся у вас кодом (даже частично работающим), чтобы помочь мне начать работу.

Будет ли следующий подход разумным? Я отошел на второй план, воссоздав логику блокировки, работая с прототипом фрейма данных.

Какие типы dtypes нуждаются в особом обращении, кроме категорий?

Конечно, использование созданного фрейма данных небезопасно, пока он не будет заполнен ...

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 не совсем уверен, какова конечная цель
можешь ли ты привести простой пример?

дальнейшее объединение с copy = False, как правило, будет отступать от этого

@jreback Я хочу использовать библиотеку cython для чтения столбца за столбцом больших объемов данных из хранилища сжатых данных, которые я хочу распаковать непосредственно в фрейм данных без промежуточного копирования из соображений производительности.

Заимствуя обычное решение numpy в таких случаях, я хочу предварительно выделить память для фрейма данных, чтобы я мог передавать указатели на эти выделенные области памяти в мою библиотеку cython, которая затем может использовать обычные c-указатели / c-массивы, соответствующие эти области памяти для непосредственного заполнения фрейма данных без промежуточных шагов копирования (или создания промежуточных объектов Python). Возможность заполнения фреймов данных несколькими потоками cython параллельно с выпущенным gil была бы дополнительным преимуществом.

В (упрощенном) псевдокоде идом будет примерно таким:

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)

Имеет ли это для вас смысл?

Насколько я понимаю, concat с copy=False не будет объединять столбцы с одинаковыми типами данных в блоки, но операции по строке вызовут это - что приведет к копированию, которого я пытаюсь избежать. Или я неправильно понял внутреннюю работу панд?

Хотя я добился некоторого прогресса в создании экземпляров больших (незаполненных) фреймов данных (коэффициент ~ 6,7), я все еще далек от огромных скоростей. Остался еще фактор ~ 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

Другая надежда заключалась в том, что этот подход может также позволить более быстрое повторное создание экземпляров DataFrames идентичной формы, когда данные небольшого объема читаются часто. До сих пор это не было основной целью, но случайно я добился большего прогресса в этом: только коэффициент ~ 4,8, чтобы перейти к скорости 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

Редактировать

Приведенные выше тайминги рисуют слишком пессимистичную картину, когда они сравнивают яблоки с апельсинами: в то время как столбец с числовой строкой создается как собственные строки фиксированной длины, эквивалентный столбец в пандах будет создан как массив объектов Python. Сравнение одного и того же подталкивает создание экземпляра DataFrame к нескольким скоростям, за исключением генерации индекса, на который приходится около 92% времени создания экземпляра.

@ ARF1, если вам нужны скорости numpy, просто используйте numpy. Я не уверен, что вы на самом деле делаете или что делаете в cython. Обычные решения заключаются в том, чтобы разбить ваши вычисления на части, передать отдельные типы данных в cython или просто получить машину большего размера.

DataFrames делают гораздо больше, чем просто рассказывают о том, как они описывают данные и манипулируют ими. Это не то, что вы на самом деле делаете с ними.

почти все операции pandas копируются. (как и большинство операций с numpy), поэтому не уверен, что вам нужно.

@jreback В настоящее время я использую numpy, но у меня есть смешанные типы dtypes, которые можно (удобно) обрабатывать только с помощью структурированных массивов. Однако структурированные массивы по своей сути упорядочены по строкам, что противоречит моему типичному аналитическому анализу, что приводит к низкой производительности. Pandas выглядит естественной альтернативой из-за его упорядочения по столбцам - если я могу получить данные в фрейм данных с хорошей скоростью.

Конечно, альтернативой было бы использование dict массивов numpy с различным типом, но это делает анализ болью, поскольку нарезка и т. Д. Больше невозможна.

Обычное решение состоит в том, чтобы разбить ваши вычисления на части и передать отдельные типы данных в cython.

Это то, что я делаю с переменной block_arrays в моем примере.

или просто купите машину побольше.

Быстрее в 100 раз - это немного финансовая проблема для меня. ;-)

@ ARF1 у вас очень странная модель того, как все работает. Обычно вы создаете небольшое количество фреймов данных, а затем работаете с ними. Скорость создания - это крошечная доля любых реальных вычислений или манипуляций.

@jreback : это не странная модель. Может быть, это странная модель, если смотреть на вещи с точки зрения чистого питона. Если вы работаете с кодом C ++, самый простой способ считывать данные в объекты python - передавать им указатели на уже существующие объекты python. Если вы делаете это в контексте, чувствительном к производительности, вам нужен дешевый и стабильный (в смысле расположения в памяти) способ создания объекта python.

Честно говоря, я не уверен, почему такое отношение распространено на досках панд. Я думаю, это прискорбно, поскольку, хотя я понимаю, что pandas - это конструкция более высокого уровня, чем numpy, людям все же может быть проще разрабатывать «поверх» pandas. DataFrame pandas на сегодняшний день является наиболее желательным типом для работы, если у вас есть код C, который хочет передать табличные данные в python, поэтому это действительно кажется важным вариантом использования.

Пожалуйста, не воспринимайте то, что я пишу отрицательно, если бы я не думал, что pandas DataFrames такие классные, я бы просто использовал numpy-записи или что-то в этом роде и покончил с этим.

@ ARF1 : В конечном счете, я не помню причин, но лучшее, что я смог сделать, это создать DataFrame для каждого числового типа из массива numpy с Copy = False, а затем снова использовать pandas.concat с Copy = False объединить их. Когда вы создаете DataFrame одного типа из массива numpy, будьте очень осторожны с ориентацией массива numpy. Если ориентация неправильная, то массивы numpy, соответствующие каждому столбцу, будут нетривиально разделены, а пандам это не нравится, и они сделают копию при первой возможности. Вы можете закрепить категории в конце, так как они не объединяются и не должны запускать какие-либо копии остальной части кадра.

Я рекомендую написать несколько модульных тестов, которые выполняют эту операцию шаг за шагом и постоянно захватывают указатели на базовые данные (через array_interface базового массива numpy) и проверяют, что они совпадают, чтобы гарантировать, что копия действительно удаляется. Панды приняли очень неудачное решение, что параметры copy / inplace НЕ должны соблюдаться. То есть, даже если вы установите, например, copy = False для конструктора DataFrame, pandas все равно выполнит копию, если решит, что это необходимо для создания DataFrame. Тот факт, что pandas делает это вместо того, чтобы бросать, когда аргументы не могут быть соблюдены, делает надежное написание кода, исключающего копии, очень утомительным и требует чрезвычайно методичной работы. Если вы не пишете модульные тесты для проверки, вы можете случайно настроить что-то позже, что приведет к созданию копии, и это произойдет незаметно и испортит вашу производительность.

@quicknir, если вы так говорите. Я думаю, вам следует просто профилировать, прежде чем пытаться что-то оптимизировать. Как я уже сказал, и, возможно, еще раз. Время строительства не должно преобладать ни над чем. Если это так, то вы просто используете DataFrame для хранения вещей, так какой смысл использовать его в первую очередь? Если он не доминирует, то в чем проблема?

@jreback Вы пишете это, предполагая, что я еще не профилировал. На самом деле, у меня есть. У нас есть код на C ++ и Python, которые десериализуют табличные данные из одного и того же формата данных. Хотя я ожидал, что код Python будет иметь небольшие накладные расходы, я решил, что разница должна быть небольшой, потому что время чтения с диска должно преобладать. Это было не так, до того, как я пошел и чрезвычайно тщательно переделал вещи, чтобы минимизировать количество копий, версия на python занимала в два раза больше или хуже по сравнению с кодом C ++, и почти все накладные расходы приходились только на создание DataFrame. Другими словами, для простого создания DataFrame определенного очень большого размера, содержимое которого меня совершенно не заботило, требовалось примерно столько же времени, сколько для чтения, распаковки и записи данных, о которых я заботился, в этот DataFrame. Это очень низкая производительность.

Если бы я был конечным пользователем этого кода, имея в виду определенные операции, возможно, то, что вы говорите о построении, не доминирующем, было бы справедливым. На самом деле я разработчик, а конечные пользователи этого кода - другие люди. Я не знаю точно, что они будут делать с DataFrame, DataFrame - это единственный способ получить представление данных на диске в памяти. Если они хотят сделать что-то очень простое с данными на диске, им все равно придется использовать формат DataFrame.

Очевидно, я мог бы поддерживать больше способов доступа к данным (например, numpy-конструкции), но это значительно увеличило бы ветвление в коде и усложнило бы мне как разработчику задачу. Если бы была какая-то фундаментальная причина, по которой DataFrames должны быть такими медленными, я бы понял и решил, поддерживать ли DataFrame, numpy или оба. Но нет реальной причины, почему это должно быть так медленно. Можно написать метод DataFrame.empty, который принимает массив кортежей, где каждый кортеж содержит имя и тип столбца, а также количество строк.

Я имею в виду разницу между поддерживающими пользователями и авторами библиотек. Проще написать собственный код, чем написать библиотеку. И проще сделать так, чтобы ваша библиотека поддерживала только пользователей, а не других авторов библиотеки. Я просто думаю, что в этом случае пустое выделение DataFrames было бы низко висящим плодом в пандах, который облегчил бы жизнь таким людям, как я и @ ARF1 .

хорошо, если вы хотите иметь разумно протестированное документированное солнце, все уши. У pandas довольно много пользователей / разработчиков. Это причина того, что DataFrame настолько универсален, и по той же причине ему требуется много проверок ошибок и выводов. Приглашаем вас увидеть, что вы можете сделать, как описано выше.

Я готов потратить некоторое время на реализацию этого, но только если есть разумный консенсус по дизайну со стороны нескольких разработчиков pandas. Если я отправлю запрос на перенос и есть определенные вещи, которые люди хотят изменить, это круто. Или, если я понимаю, после того, как потратил десять часов на это, что нет способа сделать что-то чисто, и единственный способ сделать это может включать что-то, что люди считают нежелательным, это тоже круто. Но мне не очень нравится тратить X часов, и мне говорят, что это не так полезно, реализация запутанная, мы не думаем, что ее действительно можно очистить, усложняет кодовую базу и т. Д. Я не знаю, Я не согласен с этим мнением, я раньше не вносил значительного вклада в проект OSS, поэтому не знаю, как это работает. Просто в моем первоначальном посте я начал предлагать именно это, а потом, честно говоря, у меня сложилось впечатление от вас, что это было своего рода «вне рамок» для панд.

Если вы хотите, я могу открыть новый выпуск, создать как можно более конкретное предложение по дизайну, и как только будет обратная связь / предварительное одобрение, я буду работать над ним, когда смогу.

@quicknir главное, что он должен пройти весь набор тестов, что довольно полно.

Это не выходит за рамки pandas, но API должен быть в некотором роде удобным для пользователя.

Я не уверен, почему тебе не понравилось

concat(list_of_arrays,axis=1,copy=False) Я считаю, что это делает именно то, что вы хотите (а если нет, то не совсем понятно, что вы действительно хотите).

В итоге я использовал аналогичную технику, но со списком DataFrames, которые были созданы из одного массива numpy, каждый из разных типов.

Во-первых, я думаю, что все еще сталкивался с некоторыми копиями, когда использовал эту технику. Как я уже сказал, pandas не всегда соблюдает copy = False, поэтому очень утомительно смотреть, копирует ли ваш код на самом деле или нет. Я действительно хочу, чтобы для pandas 17 разработчики рассмотрели возможность сделать copy = True значением по умолчанию, а затем copy = False выбрасывать, когда копию нельзя опустить. Но все равно.

Во-вторых, еще одна проблема заключалась в том, чтобы впоследствии изменить порядок столбцов. Это было на удивление неудобно, единственный способ сделать это без создания копии - это изначально сделать имена столбцов целыми числами, которые были упорядочены в желаемом окончательном порядке. Затем я произвел сортировку по индексу. Затем я изменил имена столбцов.

В-третьих, я обнаружил, что копии неизбежны для типов меток времени (numpy datetime64).

Я написал этот код некоторое время назад, поэтому он не свеж в моей памяти. Возможно, я сделал ошибки, но я прошел через это довольно внимательно и к таким результатам пришел в то время.

Код, который вы даете выше, не работает даже для массивов numpy. Ошибка: TypeError: невозможно объединить объект, не относящийся к NDFrame. Сначала вы должны сделать их DataFrames.

Дело не в том, что мне не нравится решение, которое вы дали здесь или выше. Мне только предстоит увидеть простой, который работает.

@quicknir ну мой пример выше работает. Пожалуйста, укажите именно то, что вы делаете, и я могу попытаться вам помочь.

pd.concat ([np.zeros ((2,2))], axis = 1, copy = False)

Я использую pandas 0.15.2, так что, возможно, это начало работать в 0.16?

Пожалуйста, прочтите строку документации pd.concat . вам нужно передать DataFrame

кстати copy=True ЕСТЬ значение по умолчанию

Правильно, вот что я написал. В фрагменте кода, который вы написали выше, был list_of_arrays, а не list_of_dataframes. Во всяком случае, я думаю, мы понимаем друг друга. В конечном итоге я использовал метод pd.concat, но он довольно нетривиальный, есть целая куча ловушек, которые сбивают с толку людей:

1) Вы должны создать список DataFrames. Каждый DataFrame должен иметь ровно один отдельный dtype. Таким образом, вам нужно собрать все различные типы данных перед тем, как начать.

2) Каждый DataFrame должен быть создан из одного массива numpy желаемого типа dtype, того же количества строк, желаемого количества столбцов и флага order = 'F'; если order = 'C' (по умолчанию), то панды часто будут делать копии, в противном случае этого не произошло бы.

3) Не обращайте внимания на 1) для категорий, они не объединяются в блок, поэтому вы можете добавить их позже.

4) Когда вы создаете все отдельные DataFrames, столбцы должны быть названы с использованием целых чисел, которые представляют порядок, в котором вы хотите их разместить. В противном случае может не быть способа изменить порядок столбцов без запуска копий.

5) Создав список DataFrames, используйте concat. Вам нужно будет тщательно проверить, что вы ничего не испортили, потому что copy = False не будет выбрасывать, если копию нельзя опустить, а скорее будет копировать без уведомления.

6) Отсортируйте индекс столбца, чтобы добиться желаемого порядка, затем переименуйте столбцы.

Я применил эту процедуру неукоснительно. Это не один лайнер, есть много мест, где можно ошибиться, я почти уверен, что он все еще не работает с отметками времени, и есть много ненужных накладных расходов, которых можно было бы избежать, не используя только интерфейс. Если хотите, я могу написать черновик того, как выглядит эта функция, используя только общедоступный API, возможно, в сочетании с некоторыми тестами, чтобы увидеть, действительно ли она исключает копии и для каких типов.

Кроме того, copy = False используется по умолчанию, например, для конструктора DataFrame. Моя основная мысль больше в том, что функция, которая не может соблюдать свои аргументы, должна бросать, а не «делать что-то разумное». То есть, если copy = False не может быть соблюден, должно быть сгенерировано исключение, чтобы пользователь знал, что он либо должен изменить другие входные данные, чтобы могло произойти копирование, либо они должны изменить копию на True. Копирование никогда не должно происходить тихо, если copy = False, это более удивительно и менее способствует поиску ошибок пользователем, заботящимся о производительности.

у вас здесь много шагов, которые не нужны
пожалуйста, покажите реальный пример, как я сделал выше

вы понимаете, что представление numpy может вернуть копию с помощью очень простых операций изменения формы (иногда), а не других

всегда будет мягкая гарантия на копию, поскольку это обычно невозможно без большого самоанализа, чтобы гарантировать это, что по определению побеждает цель простого мощного высокопроизводительного кода

Поведение copy=False в конструкции DataFrame согласуется с функцией numpy np.array (например, если вы предоставляете список массивов, окончательные данные всегда будут копировать).

Кажется, что это досадный пробел в функциях pandas. IMHO, у нас никогда не будет удовлетворительного решения с текущей моделью для внутренних компонентов pandas (которая объединяет блоки). К сожалению, это не совсем вариант для панд, потому что панды по-прежнему должны работать для людей, которые создают DataFrames с большим количеством столбцов.

Нам нужна альтернативная реализация DataFrame, разработанная специально для работы с аккуратными данными, которые хранят каждый столбец независимо в виде одномерных массивов numpy. На самом деле это несколько похоже на модель данных в xray , за исключением того, что мы позволяем столбцам быть N-мерными массивами.

Я считаю, что общий высокопроизводительный конструктор фрейма данных pandas, который только выделяет пространство, является нетривиальным, учитывая широкий спектр различных типов столбцов, которые он должен поддерживать.

При этом это кажется довольно простым для разработчиков библиотек, которые хотят использовать фреймы данных pandas в качестве высокопроизводительного контейнера данных для реализации конструктора фреймов данных только для выделения, ограниченного типами столбцов, которые им требуются.

Следующий фрагмент кода может служить источником вдохновения. Это позволяет создавать экземпляры незаполненных фреймов данных только для выделения на скоростях, близких к numpy. Обратите внимание, что для кода требуется 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)

В примере конструктора allocate_like() потеря производительности cf numpy составляет только x2,3 (обычно x333) для больших массивов и x3,3 (обычно x8,9) для массивов нулевого размера:

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

Извините, я на какое-то время забыл об этом. @ ARF1 , большое спасибо за приведенный выше пример кода. Очень приятно вместе с показателями производительности.

Я действительно чувствую, что создание класса, который соответствует макету DataFrame, без каких-либо данных, сделает код, подобный приведенному выше, намного более естественным, а также, возможно, более производительным. Этот класс также можно использовать повторно, например, при переиндексации строк.

В основном я предлагаю что-то вроде этого: класс под названием DataFrameLayout, который обертывает dtypes, имена столбцов и порядок столбцов. Например, он может хранить dict от dtype до номеров столбцов (для упорядочивания) и отдельный массив со всеми именами. Из этого макета вы можете видеть, что простая и элегантная итерация по dict позволит быстро создавать менеджеры блоков. Затем этот класс можно было бы использовать в таких местах, как пустой конструктор или в операциях переиндексации.

Я считаю, что такие абстракции необходимы для более сложных данных. В некотором смысле DataFrame является составным типом данных, а DataFrameLayout может указывать точный характер композиции.

Кстати, я думаю, что для Категорий нужно нечто подобное; то есть должна существовать абстракция CategoryType, в которой хранятся категории, независимо от того, упорядочены они или нет, тип резервного массива и т. д. То есть все, кроме фактических данных. Фактически, когда вы думаете о DataFrameLayout, вы понимаете, что все столбцы должны иметь полностью определенные типы, а это в настоящее время проблематично для категорий.

Что люди думают об этих двух классах?

@quicknir У нас уже есть класс CategoricalDtype - я согласен, что его можно расширить до полного CategoricalType вы описываете.

Я не совсем уверен насчет класса DataFrameLayout . По сути, я думаю, что мы могли бы использовать альтернативную, более простую модель данных для фреймов данных (более похожую на то, как это делается в R или Julia). Есть некоторый интерес к такого рода вещам, и я подозреваю, что в конечном итоге это произойдет в какой-то форме, но, вероятно, не в ближайшее время (и, возможно, никогда в рамках проекта pandas).

@quicknir yeh , DataFrameLayout изобретает велосипед здесь. У нас уже есть спецификация dtype, например

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

@jreback Это не изобретение велосипеда, поскольку спецификация dtype имеет несколько серьезных проблем:

1) Насколько я понимаю, to_records () выполнит глубокую копию всего DataFrame. Получение спецификации (с этого момента я буду просто использовать этот термин) для DataFrame должно быть дешевым и простым.

2) Вывод to_records - это numpy-тип. Одно из следствий этого состоит в том, что я не понимаю, как это можно расширить для правильной поддержки категорий.

3) Этот метод внутреннего хранения спецификации нелегко совместим с тем, как данные хранятся внутри DataFrame (то есть в блоках типа dtype). Создание блоков из такой спецификации включает в себя много дополнительной работы, которую можно избежать, сохранив спецификацию таким образом, как я предлагал, с dict от dtype до номеров столбцов. Когда у вас есть DataFrame с 2000 столбцами, это будет дорого.

Короче говоря, dtype представления записи - это скорее обходной путь из-за отсутствия надлежащей спецификации. Ему не хватает нескольких ключевых функций, и он намного хуже с точки зрения производительности.

На SO есть много потоков, которые просят эту функцию.

Мне кажется, что все эти проблемы связаны с тем, что BlockManager объединяет отдельные столбцы в единые блоки памяти («блоки»).
Не было бы самым простым решением не объединять данные в блоки, если указано copy = False.

У меня есть неконсолидирующийся BlockManager с исправлением обезьяны:
https://stackoverflow.com/questions/45943160/can-memmap-pandas-series-what-about-a-dataframe
что я использовал, чтобы обойти эту проблему.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги