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

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

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

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

In [7]: df.info(memory_usage="deep")

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() method:

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)
 in ?()
----> 1 if pd.Series([False, True, False]):
      2     print("I was true")

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   1578     @final
   1579     def __nonzero__(self) -> NoReturn:
-> 1580         raise ValueError(
   1581             f"The truth value of a {type(self).__name__} is ambiguous. "
   1582             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1583         )

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 operator#

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

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. Поэтому, если пользовательская функция изменяет (модифицирует) 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:10401, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
  10387 from pandas.core.apply import frame_apply
  10389 op = frame_apply(
  10390     self,
  10391     func=func,
   (...)
  10399     kwargs=kwargs,
  10400 )
> 10401 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:5410, in Series.pop(self, item)
   5385 def pop(self, item: Hashable) -> Any:
   5386     """
   5387     Return item and drops from series. Raise KeyError if not found.
   5388 
   (...)
   5408     dtype: int64
   5409     """
-> 5410     return super().pop(item=item)

File ~/work/pandas/pandas/pandas/core/generic.py:950, in NDFrame.pop(self, item)
    949 def pop(self, item: Hashable) -> Series | Any:
--> 950     result = self[item]
    951     del self[item]
    953     return result

File ~/work/pandas/pandas/pandas/core/series.py:1133, in Series.__getitem__(self, key)
   1130     return self._values[key]
   1132 elif key_is_scalar:
-> 1133     return self._get_value(key)
   1135 # Convert generator to list before going through hashable part
   1136 # (We will iterate through the generator there to check for slices)
   1137 if is_iterator(key):

File ~/work/pandas/pandas/pandas/core/series.py:1249, in Series._get_value(self, label, takeable)
   1246     return self._values[label]
   1248 # Similar to Index.get_value, but we do not fall back to positional
-> 1249 loc = self.index.get_loc(label)
   1251 if is_integer(loc):
   1252     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#

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

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

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

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

NA повышение типов для типов NumPy#

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

Класс типов

Тип данных для хранения NA

floating

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

object

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

integer

привести к float64

boolean

привести к object

Поддержка целочисленных 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 продолжает быть "числовым".

Если вам нужно представлять целые числа с возможными пропущенными значениями, используйте один из tипов данных nullable-integer, предоставляемых 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    
u    
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    
dtype: int64[pyarrow]

См. Допускающий значения null целочисленный тип данных и Функциональность 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 не является на 100% потокобезопасным. Известные проблемы связаны с 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 о порядке байтов для получения дополнительной информации.