Часто задаваемые вопросы (FAQ)
Использование памяти DataFrame
Использование памяти DataFrame (включая индекс) отображается при вызове метода info(). Параметр конфигурации display.memory_usage (см. список параметров) определяет, будет ли отображаться использование памяти DataFrame при вызове метода 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 при вызове 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)
<ipython-input-11-5c782b38cd2f> in ?()
----> 1 if pd.Series([False, True, False]):
2 print("I was true")
~/work/pandas/pandas/pandas/core/generic.py in ?(self)
1575 @final
1576 def __nonzero__(self) -> NoReturn:
-> 1577 raise ValueError(
1578 f"The truth value of a {type(self).__name__} is ambiguous. "
1579 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1580 )
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
Битовые логические операторы
Битовые логические операторы, такие как == и != возвращают логический Series, который выполняет поэлементное сравнение при сравнении со скаляром.
In [14]: s = pd.Series(range(5))
In [15]: s == 4
Out[15]:
0 False
1 False
2 False
3 False
4 True
dtype: bool
См. логические сравнения для получения дополнительных примеров.
Использование оператора in
Использование Python-оператора in на Series проверяет принадлежность к **индексу**, а не к значениям.
In [16]: s = pd.Series(range(5), index=list("abcde"))
In [17]: 2 in s
Out[17]: False
In [18]: 'b' in s
Out[18]: True
Если это поведение неожиданно, помните, что использование in на Python-словаре проверяет ключи, а не значения, а Series подобны словарям. Для проверки принадлежности к значениям используйте метод isin():
In [19]: s.isin([2])
Out[19]:
a False
b False
c True
d False
e False
dtype: bool
In [20]: s.isin([2]).any()
Out[20]: True
Для DataFrame, аналогично, in применяется к оси столбцов, проверяя принадлежность к списку имён столбцов.
Изменение с помощью пользовательских функций (UDF)
Этот раздел относится к методам pandas, которые принимают UDF. В частности, к методам DataFrame.apply(), DataFrame.aggregate(), DataFrame.transform() и DataFrame.filter().
В программировании общее правило состоит в том, что не следует изменять контейнер во время его итерации. Изменение сделает итератор недействительным, вызвав непредсказуемое поведение. Рассмотрим пример:
In [21]: values = [0, 1, 2, 3, 4, 5]
In [22]: n_removed = 0
In [23]: 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 [24]: values
Out[24]: [1, 4, 5]
Вероятно, ожидался результат [1, 3, 5]. При использовании метода pandas, который принимает UDF, pandas часто выполняет итерацию по DataFrame или другому объекту pandas. Поэтому, если UDF изменяет DataFrame, может возникнуть непредсказуемое поведение.
Вот аналогичный пример с DataFrame.apply():
In [25]: def f(s):
....: s.pop("a")
....: return s
....:
In [26]: df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
In [27]: df.apply(f, axis="columns")
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3805, in Index.get_loc(self, key)
3804 try:
-> 3805 return self._engine.get_loc(casted_key)
3806 except KeyError as err:
File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()
File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()
File pandas/_libs/hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()
File pandas/_libs/hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'a'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
Cell In[27], line 1
----> 1 df.apply(f, axis="columns")
File ~/work/pandas/pandas/pandas/core/frame.py:10374, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
10360 from pandas.core.apply import frame_apply
10362 op = frame_apply(
10363 self,
10364 func=func,
(...)
10372 kwargs=kwargs,
10373 )
> 10374 return op.apply().__finalize__(self, method="apply")
File ~/work/pandas/pandas/pandas/core/apply.py:916, in FrameApply.apply(self)
913 elif self.raw:
914 return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
--> 916 return self.apply_standard()
File ~/work/pandas/pandas/pandas/core/apply.py:1063, in FrameApply.apply_standard(self)
1061 def apply_standard(self):
1062 if self.engine == "python":
-> 1063 results, res_index = self.apply_series_generator()
1064 else:
1065 results, res_index = self.apply_series_numba()
File ~/work/pandas/pandas/pandas/core/apply.py:1081, in FrameApply.apply_series_generator(self)
1078 with option_context("mode.chained_assignment", None):
1079 for i, v in enumerate(series_gen):
1080 # ignore SettingWithCopy here in case the user mutates
-> 1081 results[i] = self.func(v, *self.args, **self.kwargs)
1082 if isinstance(results[i], ABCSeries):
1083 # If we have a view on v, we need to make a copy because
1084 # series_generator will swap out the underlying data
1085 results[i] = results[i].copy(deep=False)
Cell In[25], line 2, in f(s)
1 def f(s):
----> 2 s.pop("a")
3 return s
File ~/work/pandas/pandas/pandas/core/series.py:5391, in Series.pop(self, item)
5366 def pop(self, item: Hashable) -> Any:
5367 """
5368 Return item and drops from series. Raise KeyError if not found.
5369
(...)
5389 dtype: int64
5390 """
-> 5391 return super().pop(item=item)
File ~/work/pandas/pandas/pandas/core/generic.py:947, in NDFrame.pop(self, item)
946 def pop(self, item: Hashable) -> Series | Any:
--> 947 result = self[item]
948 del self[item]
950 return result
File ~/work/pandas/pandas/pandas/core/series.py:1121, in Series.__getitem__(self, key)
1118 return self._values[key]
1120 elif key_is_scalar:
-> 1121 return self._get_value(key)
1123 # Convert generator to list before going through hashable part
1124 # (We will iterate through the generator there to check for slices)
1125 if is_iterator(key):
File ~/work/pandas/pandas/pandas/core/series.py:1237, in Series._get_value(self, label, takeable)
1234 return self._values[label]
1236 # Similar to Index.get_value, but we do not fall back to positional
-> 1237 loc = self.index.get_loc(label)
1239 if is_integer(loc):
1240 return self._values[loc]
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3812, in Index.get_loc(self, key)
3807 if isinstance(casted_key, slice) or (
3808 isinstance(casted_key, abc.Iterable)
3809 and any(isinstance(x, slice) for x in casted_key)
3810 ):
3811 raise InvalidIndexError(key)
-> 3812 raise KeyError(key) from err
3813 except TypeError:
3814 # If we have a listlike key, _check_indexing_error will raise
3815 # InvalidIndexError. Otherwise we fall through and re-raise
3816 # the TypeError.
3817 self._check_indexing_error(key)
KeyError: 'a'
Для решения этой проблемы можно сделать копию, чтобы изменение не применялось к контейнеру, по которому выполняется итерация.
In [28]: values = [0, 1, 2, 3, 4, 5]
In [29]: n_removed = 0
In [30]: 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 [31]: values
Out[31]: [1, 3, 5]
In [32]: def f(s):
....: s = s.copy()
....: s.pop("a")
....: return s
....:
In [33]: df = pd.DataFrame({"a": [1, 2, 3], 'b': [4, 5, 6]})
In [34]: df.apply(f, axis="columns")
Out[34]:
b
0 4
1 5
2 6
Представление пропущенных значений для типов NumPy
np.nan в качестве представления NA для типов NumPy
Ввиду отсутствия NA (пропущенных) значений в NumPy и Python в целом, NA можно было бы представить с помощью:
Решения с маскированным массивом: массив данных и массив логических значений, указывающих, есть значение или оно пропущено.
Использования специального стоп-значения, битового шаблона или набора стоп-значений для обозначения
NAпо всем типам данных.
Специальное значение np.nan (Not-A-Number) было выбрано в качестве значения NA для типов NumPy, и существуют функции API, такие как DataFrame.isna() и DataFrame.notna(), которые могут использоваться для определения значений NA по всем типам данных. Однако этот выбор имеет недостаток: принудительное приведение пропущенных целочисленных данных к типу float, как показано в Поддержка целочисленных NA.
NA повышения типов для типов NumPy
При добавлении значений NA в существующий Series или DataFrame с помощью reindex() или другими средствами, типы boolean и integer будут повышены до другого типа данных для хранения значений NA. Таблица повышения приведена ниже:
Тип | Тип повышения для хранения NA |
|---|---|
| без изменений |
| без изменений |
| преобразование в |
| преобразование в |
Поддержка целочисленных NA
Отсутствует высокая производительность NA в NumPy, что является основной проблемой при представлении значений NA в целочисленных массивах. Например:
In [35]: s = pd.Series([1, 2, 3, 4, 5], index=list("abcde"))
In [36]: s
Out[36]:
a 1
b 2
c 3
d 4
e 5
dtype: int64
In [37]: s.dtype
Out[37]: dtype('int64')
In [38]: s2 = s.reindex(["a", "b", "c", "f", "u"])
In [39]: s2
Out[39]:
a 1.0
b 2.0
c 3.0
f NaN
u NaN
dtype: float64
In [40]: s2.dtype
Out[40]: dtype('float64')
Этот компромисс в основном обусловлен соображениями памяти и производительности, а также для того, чтобы полученный Series оставался «числовым».
Если вам нужно представить целые числа с потенциально пропущенными значениями, используйте один из типов данных для целочисленных значений с возможностью пропуска, предоставляемых pandas или pyarrow.
In [41]: s_int = pd.Series([1, 2, 3, 4, 5], index=list("abcde"), dtype=pd.Int64Dtype())
In [42]: s_int
Out[42]:
a 1
b 2
c 3
d 4
e 5
dtype: Int64
In [43]: s_int.dtype
Out[43]: Int64Dtype()
In [44]: s2_int = s_int.reindex(["a", "b", "c", "f", "u"])
In [45]: s2_int
Out[45]:
a 1
b 2
c 3
f <NA>
u <NA>
dtype: Int64
In [46]: s2_int.dtype
Out[46]: Int64Dtype()
In [47]: s_int_pa = pd.Series([1, 2, None], dtype="int64[pyarrow]")
In [48]: s_int_pa
Out[48]:
0 1
1 2
2 <NA>
dtype: int64[pyarrow]
См. Тип данных целочисленного значения с возможностью пропуска и Функциональность PyArrow для получения дополнительной информации.
Почему NumPy не похож на R?
Многие люди предлагали сделать NumPy похожим на NA, который присутствует в более специализированном статистическом языке программирования R. Частично это обусловлено иерархией типов NumPy:
Тип | Типы данных |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
В отличие от языка R, у него всего несколько встроенных типов данных: integer, numeric (с плавающей точкой), character, и boolean. Для NA типов используются специальные битовые шаблоны для каждого типа, используемые как пропущенное значение. Хотя это возможно сделать с полной иерархией типов NumPy, это будет более существенный компромисс (особенно для 8- и 16-битных типов данных) и большая задача по реализации.
Однако семантика R NA теперь доступна с помощью маскированных типов NumPy, таких как Int64Dtype или типы PyArrow (ArrowDtype).
Отличия от 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 [49]: x = np.array(list(range(10)), ">i4") # big endian
In [50]: newx = x.byteswap().view(x.dtype.newbyteorder()) # force native byteorder
In [51]: 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/2.2.2/user_guide/gotchas.html