Часто задаваемые вопросы (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
Для определения использования памяти объекта DataFrame метод info() использует метод memory_usage(), а также форматирует результат в удобных для чтения единицах (представление с основанием 2, то есть 1 КБ = 1024 байта).
См. также Использование памяти категориальными данными.
Использование условий if и логических выражений в 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:3812, in Index.get_loc(self, key)
3811 try:
-> 3812 return self._engine.get_loc(casted_key)
3813 except KeyError as err:
File ~/work/pandas/pandas/pandas/_libs/index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()
File ~/work/pandas/pandas/pandas/_libs/index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()
File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()
File pandas/_libs/hashtable_class_helper.pxi:7096, 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:10381, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
10367 from pandas.core.apply import frame_apply
10369 op = frame_apply(
10370 self,
10371 func=func,
(...)
10379 kwargs=kwargs,
10380 )
> 10381 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:5402, in Series.pop(self, item)
5377 def pop(self, item: Hashable) -> Any:
5378 """
5379 Return item and drops from series. Raise KeyError if not found.
5380
(...)
5400 dtype: int64
5401 """
-> 5402 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:1130, in Series.__getitem__(self, key)
1127 return self._values[key]
1129 elif key_is_scalar:
-> 1130 return self._get_value(key)
1132 # Convert generator to list before going through hashable part
1133 # (We will iterate through the generator there to check for slices)
1134 if is_iterator(key):
File ~/work/pandas/pandas/pandas/core/series.py:1246, in Series._get_value(self, label, takeable)
1243 return self._values[label]
1245 # Similar to Index.get_value, but we do not fall back to positional
-> 1246 loc = self.index.get_loc(label)
1248 if is_integer(loc):
1249 return self._values[loc]
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3819, in Index.get_loc(self, key)
3814 if isinstance(casted_key, slice) or (
3815 isinstance(casted_key, abc.Iterable)
3816 and any(isinstance(x, slice) for x in casted_key)
3817 ):
3818 raise InvalidIndexError(key)
-> 3819 raise KeyError(key) from err
3820 except TypeError:
3821 # If we have a listlike key, _check_indexing_error will raise
3822 # InvalidIndexError. Otherwise we fall through and re-raise
3823 # the TypeError.
3824 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
Поскольку в NumPy и Python в целом изначально нет поддержки NA (пропущенных значений), NA можно было бы представить следующим образом:
С помощью маскированного массива: массива данных и массива логических значений, указывающих, присутствует значение или пропущено.
С помощью специального значения-маркера, битового шаблона или набора значений-маркеров для обозначения
NAво всех типах данных.
Специальное значение np.nan (Not-A-Number, «не число») было выбрано в качестве значения NA для типов NumPy. Для обнаружения значений NA во всех типах данных можно использовать такие функции API, как DataFrame.isna() и DataFrame.notna(). Однако у этого решения есть недостаток: пропущенные целочисленные данные преобразуются в типы с плавающей точкой, как показано в разделе Поддержка целочисленных NA.
Преобразование типов NA для типов NumPy
При добавлении NA в существующий объект Series или DataFrame с помощью reindex() или другим способом логические и целочисленные типы преобразуются в другой тип данных, позволяющий хранить NA. Преобразования обобщены в таблице:
Класс типов | Тип данных для хранения NA |
|---|---|
| без изменений |
| без изменений |
| преобразуется в |
| преобразуется в |
Поддержка целочисленных NA
Поскольку в NumPy изначально нет встроенной высокопроизводительной поддержки NA, главным ограничением становится невозможность представлять 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]
Подробнее см. в разделах Тип данных Nullable integer и Возможности PyArrow.
Почему бы не сделать NumPy похожим на R?
Многие предлагали, чтобы NumPy просто реализовал поддержку NA, имеющуюся в более специализированном статистическом языке программирования R. Одна из причин заключается в иерархии типов NumPy.
В отличие от NumPy, язык R имеет лишь несколько встроенных типов данных: integer, numeric (с плавающей точкой), character и boolean. Типы NA реализуются путём резервирования специальных битовых шаблонов для каждого типа, используемых в качестве пропущенного значения. Это было бы возможно и для всей иерархии типов NumPy, но потребовало бы более существенных компромиссов (особенно для 8- и 16-битных типов данных) и значительных усилий по реализации.
Тем не менее семантику NA из R теперь можно использовать с маскированными типами NumPy, такими как Int64Dtype, или с типами PyArrow (ArrowDtype).
Отличия от NumPy
Для объектов Series и DataFrame метод var() нормирует результат на N-1, чтобы получить несмещённые оценки дисперсии генеральной совокупности. В то же время функция NumPy numpy.var() нормирует результат на N, измеряя дисперсию выборки. Обратите внимание, что и в pandas, и в NumPy метод cov() нормирует результат на N-1.
Потокобезопасность
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–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/gotchas.html