Часто задаваемые вопросы (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
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 |
|---|---|
| без изменений |
| без изменений |
| преобразовано к |
| преобразовано к |
Хотя это может показаться большой уступкой, я на практике нашел очень мало случаев, когда это вызывает проблемы, например, при хранении значений, больших чем 2**53. Некоторые объяснения мотивации приведены в следующем разделе.
Почему не сделать NumPy похожим на R?
Многие люди предлагали, чтобы NumPy просто эмулировал NA поддержку, присутствующую в более специализированном статистическом языке программирования R. Частью причины является иерархия типов NumPy:
Тип данных | Типы данных |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Язык 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