Spec-Zone.ru › pandas 1

Часто задаваемые вопросы (FAQ)

Использование памяти DataFrame

Использование памяти DataFrame (включая индекс) отображается при вызове info(). Параметр конфигурации display.memory_usage (см. список параметров) указывает, будет ли отображаться использование памяти DataFrame при вызове метода df.info().

Например, использование памяти DataFrame ниже отображается при вызове info():

In [1]: dtypes = [
   ...:     "int64",
   ...:     "float64",
   ...:     "datetime64[ns]",
   ...:     "timedelta64[ns]",
   ...:     "complex128",
   ...:     "object",
   ...:     "bool",
   ...: ]
   ...: 

In [2]: n = 5000

In [3]: data = {t: np.random.randint(100, size=n).astype(t) for t in dtypes}

In [4]: df = pd.DataFrame(data)

In [5]: df["categorical"] = df["object"].astype("category")

In [6]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
 #   Column           Non-Null Count  Dtype          
---  ------           --------------  -----          
 0   int64            5000 non-null   int64          
 1   float64          5000 non-null   float64        
 2   datetime64[ns]   5000 non-null   datetime64[ns] 
 3   timedelta64[ns]  5000 non-null   timedelta64[ns]
 4   complex128       5000 non-null   complex128     
 5   object           5000 non-null   object         
 6   bool             5000 non-null   bool           
 7   categorical      5000 non-null   category       
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 288.2+ KB

Символ + указывает, что фактическое использование памяти может быть выше, потому что pandas не учитывает память, используемую значениями в столбцах с dtype=object.

Передача memory_usage='deep' позволит получить более точный отчет об использовании памяти, учитывая полное использование содержащихся объектов. Это необязательно, так как может быть дорогостоящим для глубокого анализа.

In [7]: df.info(memory_usage="deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
 #   Column           Non-Null Count  Dtype          
---  ------           --------------  -----          
 0   int64            5000 non-null   int64          
 1   float64          5000 non-null   float64        
 2   datetime64[ns]   5000 non-null   datetime64[ns] 
 3   timedelta64[ns]  5000 non-null   timedelta64[ns]
 4   complex128       5000 non-null   complex128     
 5   object           5000 non-null   object         
 6   bool             5000 non-null   bool           
 7   categorical      5000 non-null   category       
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 424.7 KB

По умолчанию параметр отображения установлен на True, но его можно явно переопределить, передав аргумент memory_usage при вызове df.info().

Использование памяти каждого столбца можно найти, вызвав метод memory_usage(). Это возвращает Series с индексом, представленным именами столбцов, и объемом памяти каждого столбца в байтах. Для DataFrame выше, использование памяти каждого столбца и общее использование памяти можно найти с помощью метода memory_usage:

In [8]: df.memory_usage()
Out[8]: 
Index                128
int64              40000
float64            40000
datetime64[ns]     40000
timedelta64[ns]    40000
complex128         80000
object             40000
bool                5000
categorical         9968
dtype: int64

# total memory usage of dataframe
In [9]: df.memory_usage().sum()
Out[9]: 295096

По умолчанию, объем памяти индекса DataFrame отображается в возвращаемом Series, использование памяти индекса можно отключить, передав аргумент index=False:

In [10]: df.memory_usage(index=False)
Out[10]: 
int64              40000
float64            40000
datetime64[ns]     40000
timedelta64[ns]    40000
complex128         80000
object             40000
bool                5000
categorical         9968
dtype: int64

Использование памяти, отображаемое методом info(), использует метод memory_usage() для определения использования памяти DataFrame, а также форматирует вывод в удобочитаемых единицах (представление в системе с основанием 2; т. е. 1 КБ = 1024 байта).

См. также Использование памяти категориальных данных.

Использование операторов if/truth с pandas

pandas следует соглашению NumPy и генерирует ошибку, когда вы пытаетесь преобразовать что-либо в bool. Это происходит в операторе if или при использовании логических операций: and, or, и not. Непонятно, каким должен быть результат следующего кода:

>>> if pd.Series([False, True, False]):
...     pass

Должен ли он быть True, потому что он не пустой, или False, потому что есть False значения? Непонятно, поэтому pandas генерирует ошибку ValueError.

In [11]: if pd.Series([False, True, False]):
   ....:     print("I was true")
   ....: 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [11], line 1
----> 1 if pd.Series([False, True, False]):
      2     print("I was true")

File ~/work/pandas/pandas/pandas/core/generic.py:1526, in NDFrame.__nonzero__(self)
   1524 @final
   1525 def __nonzero__(self) -> NoReturn:
-> 1526     raise ValueError(
   1527         f"The truth value of a {type(self).__name__} is ambiguous. "
   1528         "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1529     )

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Необходимо явно указать, что вы хотите сделать с DataFrame, например, использовать any(), all() или empty(). В качестве альтернативы, вы можете сравнить, является ли объект pandas None:

In [12]: if pd.Series([False, True, False]) is not None:
   ....:     print("I was not None")
   ....: 
I was not None

Ниже показано, как проверить, являются ли какие-либо значения True:

In [13]: if pd.Series([False, True, False]).any():
   ....:     print("I am any")
   ....: 
I am any

Чтобы оценить одноэлементные объекты pandas в контексте булева выражения, используйте метод bool():

In [14]: pd.Series([True]).bool()
Out[14]: True

In [15]: pd.Series([False]).bool()
Out[15]: False

In [16]: pd.DataFrame([[True]]).bool()
Out[16]: True

In [17]: pd.DataFrame([[False]]).bool()
Out[17]: False

Битовые логические операции

Битовые логические операторы, такие как == и != возвращают булевый Series, который выполняет элементное сравнение при сравнении со скаляром.

In [18]: s = pd.Series(range(5))

In [19]: s == 4
Out[19]: 
0    False
1    False
2    False
3    False
4     True
dtype: bool

См. логические сравнения для получения дополнительных примеров.

Использование оператора in

Использование оператора Python in для Series проверяет членство в индексе, а не членство среди значений.

In [20]: s = pd.Series(range(5), index=list("abcde"))

In [21]: 2 in s
Out[21]: False

In [22]: 'b' in s
Out[22]: True

Если это поведение неожиданно, помните, что использование in на словаре Python проверяет ключи, а не значения, и Series похожи на словари. Чтобы проверить членство в значениях, используйте метод isin():

In [23]: s.isin([2])
Out[23]: 
a    False
b    False
c     True
d    False
e    False
dtype: bool

In [24]: s.isin([2]).any()
Out[24]: True

Для DataFrame, аналогично, in применяется к оси столбцов, проверяя членство в списке имён столбцов.

Изменение с помощью пользовательских функций (UDF)

Этот раздел относится к методам pandas, которые принимают UDF. В частности, к методам .apply, .aggregate, .transform, и .filter.

В программировании существует общее правило, что не следует изменять контейнер во время его итерации. Изменение нарушит итератор, что приведёт к неожиданному поведению. Рассмотрим пример:

In [25]: values = [0, 1, 2, 3, 4, 5]

In [26]: n_removed = 0

In [27]: for k, value in enumerate(values):
   ....:     idx = k - n_removed
   ....:     if value % 2 == 1:
   ....:         del values[idx]
   ....:         n_removed += 1
   ....:     else:
   ....:         values[idx] = value + 1
   ....: 

In [28]: values
Out[28]: [1, 4, 5]

Вероятно, ожидался результат [1, 3, 5]. При использовании метода pandas, который принимает UDF, pandas часто выполняет итерацию по DataFrame или другому объекту pandas. Поэтому, если UDF изменяет DataFrame, могут возникнуть неожиданные проблемы.

Вот аналогичный пример с DataFrame.apply():

In [29]: def f(s):
   ....:     s.pop("a")
   ....:     return s
   ....: 

In [30]: df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})

In [31]: try:
   ....:     df.apply(f, axis="columns")
   ....: except Exception as err:
   ....:     print(repr(err))
   ....: 
KeyError('a')

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

In [32]: values = [0, 1, 2, 3, 4, 5]

In [33]: n_removed = 0

In [34]: for k, value in enumerate(values.copy()):
   ....:     idx = k - n_removed
   ....:     if value % 2 == 1:
   ....:         del values[idx]
   ....:         n_removed += 1
   ....:     else:
   ....:         values[idx] = value + 1
   ....: 

In [35]: values
Out[35]: [1, 3, 5]
In [36]: def f(s):
   ....:     s = s.copy()
   ....:     s.pop("a")
   ....:     return s
   ....: 

In [37]: df = pd.DataFrame({"a": [1, 2, 3], 'b': [4, 5, 6]})

In [38]: df.apply(f, axis="columns")
Out[38]: 
   b
0  4
1  5
2  6

NaN, целочисленные NA значения и NA преобразования типов

Выбор NA представления

Из-за отсутствия NA (пропущенных) значений поддержки NumPy и Python в целом, нам пришлось сделать сложный выбор между:

  • Решение с маскированным массивом: массив данных и массив булевых значений, указывающих, есть ли значение или оно пропущено.

  • Использование специального значения-маяка, битового шаблона или набора значений-маяков для обозначения NA через все типы данных.

По многим причинам мы выбрали второй вариант. После многих лет использования в производстве он доказал, по крайней мере, на мой взгляд, что это лучшее решение, учитывая состояние дел в NumPy и Python в целом. Специальное значение NaN (Not-A-Number) используется везде как NA значение, и существуют функции API DataFrame.isna() и DataFrame.notna(), которые могут использоваться для всех типов данных для обнаружения значений NA.

Однако это связано с несколькими компромиссами, которые я, безусловно, не игнорировал.

Поддержка целочисленных NA

В отсутствие высокопроизводительной NA поддержки, встроенной в NumPy с самого начала, основной жертвой является возможность представлять NAs в целочисленных массивах. Например:

In [39]: s = pd.Series([1, 2, 3, 4, 5], index=list("abcde"))

In [40]: s
Out[40]: 
a    1
b    2
c    3
d    4
e    5
dtype: int64

In [41]: s.dtype
Out[41]: dtype('int64')

In [42]: s2 = s.reindex(["a", "b", "c", "f", "u"])

In [43]: s2
Out[43]: 
a    1.0
b    2.0
c    3.0
f    NaN
u    NaN
dtype: float64

In [44]: s2.dtype
Out[44]: dtype('float64')

Этот компромисс в основном сделан по причинам памяти и производительности, а также для того, чтобы полученный Series оставался «числовым».

Если вам нужно представить целые числа с возможными пропущенными значениями, используйте один из поддерживаемых типов данных для целых чисел с пропусками, предоставляемых pandas

  • Int8Dtype

  • Int16Dtype

  • Int32Dtype

  • Int64Dtype

In [45]: s_int = pd.Series([1, 2, 3, 4, 5], index=list("abcde"), dtype=pd.Int64Dtype())

In [46]: s_int
Out[46]: 
a    1
b    2
c    3
d    4
e    5
dtype: Int64

In [47]: s_int.dtype
Out[47]: Int64Dtype()

In [48]: s2_int = s_int.reindex(["a", "b", "c", "f", "u"])

In [49]: s2_int
Out[49]: 
a       1
b       2
c       3
f    <NA>
u    <NA>
dtype: Int64

In [50]: s2_int.dtype
Out[50]: Int64Dtype()

См. Тип данных для целых чисел с пропусками для получения дополнительной информации.

NA преобразования типов

При вводе значений NA в существующий Series или DataFrame с помощью reindex() или другими способами, типы boolean и integer будут преобразованы в другой тип данных для хранения NA. Преобразования обобщены в этой таблице:

Тип данных

Преобразованный тип данных для хранения значений NA

floating

без изменений

object

без изменений

integer

преобразовано к float64

boolean

преобразовано к object

Хотя это может показаться большой уступкой, я на практике нашел очень мало случаев, когда это вызывает проблемы, например, при хранении значений, больших чем 2**53. Некоторые объяснения мотивации приведены в следующем разделе.

Почему не сделать NumPy похожим на R?

Многие люди предлагали, чтобы NumPy просто эмулировал NA поддержку, присутствующую в более специализированном статистическом языке программирования R. Частью причины является иерархия типов NumPy:

Тип данных

Типы данных

numpy.floating

float16, float32, float64, float128

numpy.integer

int8, int16, int32, int64

numpy.unsignedinteger

uint8, uint16, uint32, uint64

numpy.object_

object_

numpy.bool_

bool_

numpy.character

string_, unicode_

Язык R, в отличие от этого, имеет только несколько встроенных типов данных: integer, numeric (с плавающей запятой), character, и boolean. Типы NA реализуются путем резервирования специальных битовых шаблонов для каждого типа, который используется в качестве пропущенного значения. Хотя это возможно и с полной иерархией типов NumPy, это было бы более значительным компромиссом (особенно для 8- и 16-битных типов данных) и более сложной задачей реализации.

Альтернативный подход — использование маскированных массивов. Маскированный массив — это массив данных с ассоциированной булевой маской, обозначающей, следует ли рассматривать каждое значение как NA или нет. Лично я не очень люблю этот подход, так как считаю, что в целом он накладывает довольно большую нагрузку на пользователя и разработчика библиотеки. Кроме того, он влечет за собой довольно высокую стоимость производительности при работе с числовыми данными по сравнению с простым подходом, использующим NaN. Поэтому я выбрал подход Python — «практичность важнее чистоты» — и пожертвовал возможностью представления целочисленных NA для гораздо более простого подхода с использованием специального значения в массивах с плавающей точкой и объектах для обозначения NA, и преобразования целочисленных массивов в массивы с плавающей запятой, когда необходимо ввести NA.

Отличия от NumPy

Для объектов Series и DataFrame, var() нормализуется с помощью N-1 для получения несмещенных оценок дисперсии генеральной совокупности, в то время как NumPy numpy.var() нормализуется по N, что определяет дисперсию выборки. Обратите внимание, что cov() нормализуется по N-1 как в pandas, так и в NumPy.

Потокобезопасность

pandas не является полностью потокобезопасным. Известные проблемы связаны с методом copy(). Если вы выполняете много копий объектов DataFrame, используемых в нескольких потоках, рекомендуется использовать блокировки в потоках, где происходит копирование данных.

См. данную ссылку для получения дополнительной информации.

Проблемы с порядком байтов

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

Traceback
    ...
ValueError: Big-endian buffer not supported on little-endian compiler

Для решения этой проблемы необходимо преобразовать базовый массив NumPy в порядок байтов локальной системы до передачи его конструкторам Series или DataFrame с помощью чего-то подобного:

In [51]: x = np.array(list(range(10)), ">i4")  # big endian

In [52]: newx = x.byteswap().newbyteorder()  # force native byteorder

In [53]: s = pd.Series(newx)

См. документацию NumPy по порядку байтов для получения более подробной информации.

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/1.5.0/user_guide/gotchas.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API