Часто задаваемые вопросы (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 |
|---|---|
|
без изменений |
|
без изменений |
|
привести к |
|
привести к |
Поддержка целочисленных 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 о порядке байтов для получения дополнительной информации.