Spec-Zone.ru › pandas 2

Часто задаваемые вопросы (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

floating

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

object

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

integer

преобразуется в float64

boolean

преобразуется в object

Поддержка целочисленных 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 для целых чисел с поддержкой пропущенных значений:

  • Int8Dtype

  • Int16Dtype

  • Int32Dtype

  • Int64Dtype

  • ArrowDtype

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

Spec-Zone.ru

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