Spec-Zone.ru › pandas 2

Вступление в структуры данных

Мы начнем с краткого, неполного обзора основных структур данных в pandas, чтобы вы могли начать работу. Основные особенности типов данных, индексации, маркировки осей и выравнивания применимы ко всем объектам. Для начала импортируйте NumPy и загрузите pandas в ваше пространство имен:

In [1]: import numpy as np

In [2]: import pandas as pd

В основе своей, выравнивание данных является неотъемлемым. Связь между метками и данными не будет нарушена, если вы этого явно не сделаете.

Мы дадим краткое введение в структуры данных, а затем рассмотрим все широкие категории функциональности и методов в отдельных разделах.

Серии

Series — это одномерный маркированный массив, способный хранить любые типы данных (целые числа, строки, числа с плавающей точкой, объекты Python и т. д.). Метки осей вместе называются индексом. Основной метод создания Series состоит в вызове:

s = pd.Series(data, index=index)

Здесь data может быть многими разными вещами:

  • словарь Python

  • массив ndarray

  • скалярное значение (например, 5)

Переданный индекс — это список меток осей. Таким образом, это разделяется на несколько случаев в зависимости от того, что представляет собой данные:

Из ndarray

Если data является массивом ndarray, индекс должен иметь такую же длину, как и данные. Если индекс не передан, будет создан индекс со значениями [0, ..., len(data) - 1].

In [3]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [4]: s
Out[4]: 
a    0.469112
b   -0.282863
c   -1.509059
d   -1.135632
e    1.212112
dtype: float64

In [5]: s.index
Out[5]: Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

In [6]: pd.Series(np.random.randn(5))
Out[6]: 
0   -0.173215
1    0.119209
2   -1.044236
3   -0.861849
4   -2.104569
dtype: float64

Примечание

pandas поддерживает не уникальные значения индекса. Если будет попытка операции, не поддерживающей дубликаты значений индекса, будет выброшено исключение.

Из словаря

Series может быть создан из словарей:

In [7]: d = {"b": 1, "a": 0, "c": 2}

In [8]: pd.Series(d)
Out[8]: 
b    1
a    0
c    2
dtype: int64

Если передан индекс, значения в данных, соответствующие меткам в индексе, будут извлечены.

In [9]: d = {"a": 0.0, "b": 1.0, "c": 2.0}

In [10]: pd.Series(d)
Out[10]: 
a    0.0
b    1.0
c    2.0
dtype: float64

In [11]: pd.Series(d, index=["b", "c", "d", "a"])
Out[11]: 
b    1.0
c    2.0
d    NaN
a    0.0
dtype: float64

Примечание

NaN (не число) — это стандартный маркер пропущенных данных, используемый в pandas.

Из скалярного значения

Если data — это скалярное значение, должен быть передан индекс. Значение будет повторяться, чтобы соответствовать длине индекса.

In [12]: pd.Series(5.0, index=["a", "b", "c", "d", "e"])
Out[12]: 
a    5.0
b    5.0
c    5.0
d    5.0
e    5.0
dtype: float64

Серия как ndarray

Series ведет себя очень похоже на ndarray и является допустимым аргументом для большинства функций NumPy. Однако операции, такие как срезы, также будут производить срезы индекса.

In [13]: s.iloc[0]
Out[13]: 0.4691122999071863

In [14]: s.iloc[:3]
Out[14]: 
a    0.469112
b   -0.282863
c   -1.509059
dtype: float64

In [15]: s[s > s.median()]
Out[15]: 
a    0.469112
e    1.212112
dtype: float64

In [16]: s.iloc[[4, 3, 1]]
Out[16]: 
e    1.212112
d   -1.135632
b   -0.282863
dtype: float64

In [17]: np.exp(s)
Out[17]: 
a    1.598575
b    0.753623
c    0.221118
d    0.321219
e    3.360575
dtype: float64

Примечание

Мы рассмотрим индексирование на основе массивов, например, s.iloc[[4, 3, 1]] в разделе по индексированию.

Как и массив NumPy, pandas Series имеет единственный dtype.

In [18]: s.dtype
Out[18]: dtype('float64')

Часто это тип данных NumPy. Однако pandas и сторонние библиотеки расширяют систему типов NumPy в нескольких местах, в этом случае тип данных будет ExtensionDtype. Некоторые примеры в pandas — категориальные данные и тип данных целочисленных значений с допусками NaN. Более подробную информацию см. в dtypes.

Если вам нужен фактический массив, лежащий в основе Series, используйте Series.array.

In [19]: s.array
Out[19]: 
<NumpyExtensionArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

Обращение к массиву может быть полезно, когда вам нужно выполнить операцию без индекса (например, чтобы отключить автоматическое выравнивание).

Series.array всегда будет ExtensionArray. Вкратце, ExtensionArray — это тонкий обертка вокруг одного или нескольких конкретных массивов, таких как numpy.ndarray. pandas знает, как взять ExtensionArray и сохранить его в Series или столбце DataFrame. Более подробную информацию см. в dtypes.

Хотя Series похож на ndarray, если вам нужен фактический ndarray, используйте Series.to_numpy().

In [20]: s.to_numpy()
Out[20]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

Даже если Series поддерживается ExtensionArray, Series.to_numpy() вернёт массив NumPy.

Серия как словарь

Серия также похожа на словарь фиксированного размера, так как вы можете получать и устанавливать значения по меткам индекса:

In [21]: s["a"]
Out[21]: 0.4691122999071863

In [22]: s["e"] = 12.0

In [23]: s
Out[23]: 
a     0.469112
b    -0.282863
c    -1.509059
d    -1.135632
e    12.000000
dtype: float64

In [24]: "e" in s
Out[24]: True

In [25]: "f" in s
Out[25]: False

Если метка не содержится в индексе, возникает исключение:

In [26]: s["f"]
---------------------------------------------------------------------------
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: 'f'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[26], line 1
----> 1 s["f"]

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: 'f'

Используя метод Series.get(), отсутствующая метка возвращает None или заданное значение по умолчанию:

In [27]: s.get("f")

In [28]: s.get("f", np.nan)
Out[28]: nan

Эти метки также можно получить по атрибутам.

Векторные операции и выравнивание меток с Series

При работе с сырыми массивами NumPy обычно нет необходимости в итерации по значениям. То же самое справедливо и при работе с Series в pandas. Series также можно передать в большинство методов NumPy, ожидающих ndarray.

In [29]: s + s
Out[29]: 
a     0.938225
b    -0.565727
c    -3.018117
d    -2.271265
e    24.000000
dtype: float64

In [30]: s * 2
Out[30]: 
a     0.938225
b    -0.565727
c    -3.018117
d    -2.271265
e    24.000000
dtype: float64

In [31]: np.exp(s)
Out[31]: 
a         1.598575
b         0.753623
c         0.221118
d         0.321219
e    162754.791419
dtype: float64

Ключевое различие между Series и ndarray заключается в том, что операции между Series автоматически выравнивают данные на основе меток. Таким образом, вы можете записывать вычисления, не учитывая, имеют ли участвующие Series одинаковые метки.

In [32]: s.iloc[1:] + s.iloc[:-1]
Out[32]: 
a         NaN
b   -0.565727
c   -3.018117
d   -2.271265
e         NaN
dtype: float64

Результат операции между невыровненными Series будет содержать объединение вовлечённых индексов. Если метка не найдена в одном Series или другом, результат будет помечен как отсутствующий NaN. Возможность записи кода без явного выравнивания данных даёт огромную свободу и гибкость в интерактивном анализе данных и исследованиях. Интегрированные возможности выравнивания данных в структурах данных pandas выделяют pandas среди большинства аналогичных инструментов для работы с маркированными данными.

Примечание

В общем, мы решили сделать результатом по умолчанию операций между объектами с различными индексами объединение индексов, чтобы избежать потери информации. Наличие метки индекса, даже если данные отсутствуют, обычно является важной информацией в рамках вычисления. Конечно, у вас есть возможность исключить метки с отсутствующими данными с помощью функции dropna.

END_OF_DOCUMENT_MARKER

Атрибут Name

Series также имеет атрибут name.

In [33]: s = pd.Series(np.random.randn(5), name="something")

In [34]: s
Out[34]: 
0   -0.494929
1    1.071804
2    0.721555
3   -0.706771
4   -1.039575
Name: something, dtype: float64

In [35]: s.name
Out[35]: 'something'

Атрибут Series name может быть присвоен автоматически во многих случаях, в частности, при выборе одного столбца из DataFrame, атрибуту name будет присвоено имя столбца.

Вы можете переименовать Series с помощью метода pandas.Series.rename().

In [36]: s2 = s.rename("different")

In [37]: s2.name
Out[37]: 'different'

Обратите внимание, что s и s2 ссылаются на разные объекты.

DataFrame

DataFrame — двумерная меченная структура данных со столбцами потенциально разных типов. Вы можете представить её как электронную таблицу или таблицу SQL, или словарь объектов Series. Она, как правило, является наиболее часто используемым объектом pandas. Как и Series, DataFrame принимает множество различных типов входных данных:

  • Словарь 1D массивов, списков, словарей или Series

  • 2-мерный массив numpy.ndarray

  • Структурированный или записывающий массив ndarray

  • Series

  • Другой DataFrame

Вместе с данными вы можете необязательно передать аргументы индекс (метки строк) и столбцы (метки столбцов). Если вы передаете индекс и/или столбцы, вы гарантируете индекс и/или столбцы результирующего DataFrame. Таким образом, словарь Series плюс определенный индекс отбросит все данные, не соответствующие переданному индексу.

Если метки осей не переданы, они будут построены из входных данных на основе общих правил.

Из словаря Series или словарей

Результирующий индекс будет объединением индексов различных Series. Если есть вложенные словари, они сначала преобразуются в Series. Если столбцы не переданы, столбцы будут упорядоченным списком ключей словаря.

In [38]: d = {
   ....:     "one": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),
   ....:     "two": pd.Series([1.0, 2.0, 3.0, 4.0], index=["a", "b", "c", "d"]),
   ....: }
   ....: 

In [39]: df = pd.DataFrame(d)

In [40]: df
Out[40]: 
   one  two
a  1.0  1.0
b  2.0  2.0
c  3.0  3.0
d  NaN  4.0

In [41]: pd.DataFrame(d, index=["d", "b", "a"])
Out[41]: 
   one  two
d  NaN  4.0
b  2.0  2.0
a  1.0  1.0

In [42]: pd.DataFrame(d, index=["d", "b", "a"], columns=["two", "three"])
Out[42]: 
   two three
d  4.0   NaN
b  2.0   NaN
a  1.0   NaN

Метки строк и столбцов можно получить соответственно, обратившись к атрибутам индекс и столбцы:

Примечание

Когда определенный набор столбцов передается вместе со словарем данных, переданные столбцы перезаписывают ключи в словаре.

In [43]: df.index
Out[43]: Index(['a', 'b', 'c', 'd'], dtype='object')

In [44]: df.columns
Out[44]: Index(['one', 'two'], dtype='object')

Из словаря массивов/списков

Все массивы должны иметь одинаковую длину. Если передан индекс, он также должен иметь такую же длину, как и массивы. Если индекс не передан, результатом будет range(n), где n — длина массива.

In [45]: d = {"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]}

In [46]: pd.DataFrame(d)
Out[46]: 
   one  two
0  1.0  4.0
1  2.0  3.0
2  3.0  2.0
3  4.0  1.0

In [47]: pd.DataFrame(d, index=["a", "b", "c", "d"])
Out[47]: 
   one  two
a  1.0  4.0
b  2.0  3.0
c  3.0  2.0
d  4.0  1.0

Из структурированного или записывающего массива

Этот случай обрабатывается аналогично словарю массивов.

In [48]: data = np.zeros((2,), dtype=[("A", "i4"), ("B", "f4"), ("C", "a10")])

In [49]: data[:] = [(1, 2.0, "Hello"), (2, 3.0, "World")]

In [50]: pd.DataFrame(data)
Out[50]: 
   A    B         C
0  1  2.0  b'Hello'
1  2  3.0  b'World'

In [51]: pd.DataFrame(data, index=["first", "second"])
Out[51]: 
        A    B         C
first   1  2.0  b'Hello'
second  2  3.0  b'World'

In [52]: pd.DataFrame(data, columns=["C", "A", "B"])
Out[52]: 
          C  A    B
0  b'Hello'  1  2.0
1  b'World'  2  3.0

Примечание

DataFrame не предназначен для работы точно так же, как двумерный массив NumPy.

Из списка словарей

In [53]: data2 = [{"a": 1, "b": 2}, {"a": 5, "b": 10, "c": 20}]

In [54]: pd.DataFrame(data2)
Out[54]: 
   a   b     c
0  1   2   NaN
1  5  10  20.0

In [55]: pd.DataFrame(data2, index=["first", "second"])
Out[55]: 
        a   b     c
first   1   2   NaN
second  5  10  20.0

In [56]: pd.DataFrame(data2, columns=["a", "b"])
Out[56]: 
   a   b
0  1   2
1  5  10

Из словаря кортежей

Вы можете автоматически создать фрейм с многоуровневым индексом, передав словарь кортежей.

In [57]: pd.DataFrame(
   ....:     {
   ....:         ("a", "b"): {("A", "B"): 1, ("A", "C"): 2},
   ....:         ("a", "a"): {("A", "C"): 3, ("A", "B"): 4},
   ....:         ("a", "c"): {("A", "B"): 5, ("A", "C"): 6},
   ....:         ("b", "a"): {("A", "C"): 7, ("A", "B"): 8},
   ....:         ("b", "b"): {("A", "D"): 9, ("A", "B"): 10},
   ....:     }
   ....: )
   ....: 
Out[57]: 
       a              b      
       b    a    c    a     b
A B  1.0  4.0  5.0  8.0  10.0
  C  2.0  3.0  6.0  7.0   NaN
  D  NaN  NaN  NaN  NaN   9.0

Из Series

Результатом будет DataFrame с тем же индексом, что и входной Series, и одним столбцом, имя которого — исходное имя Series (только если не указано другое имя столбца).

In [58]: ser = pd.Series(range(3), index=list("abc"), name="ser")

In [59]: pd.DataFrame(ser)
Out[59]: 
   ser
a    0
b    1
c    2

Из списка именованных кортежей

Имена полей первого namedtuple в списке определяют столбцы DataFrame. Остальные именованные кортежи (или кортежи) просто распаковываются, и их значения подаются в строки DataFrame. Если какой-либо из этих кортежей короче, чем первый namedtuple, то последующие столбцы в соответствующей строке помечаются как пропущенные значения. Если какой-либо из них длиннее первого namedtuple, возникает ValueError.

In [60]: from collections import namedtuple

In [61]: Point = namedtuple("Point", "x y")

In [62]: pd.DataFrame([Point(0, 0), Point(0, 3), (2, 3)])
Out[62]: 
   x  y
0  0  0
1  0  3
2  2  3

In [63]: Point3D = namedtuple("Point3D", "x y z")

In [64]: pd.DataFrame([Point3D(0, 0, 0), Point3D(0, 3, 5), Point(2, 3)])
Out[64]: 
   x  y    z
0  0  0  0.0
1  0  3  5.0
2  2  3  NaN

Из списка DataClass

Data Class, представленные в PEP557, могут быть переданы в конструктор DataFrame. Передача списка DataClass эквивалентна передаче списка словарей.

Обратите внимание, что все значения в списке должны быть DataClass, смешивание типов в списке приведет к TypeError.

In [65]: from dataclasses import make_dataclass

In [66]: Point = make_dataclass("Point", [("x", int), ("y", int)])

In [67]: pd.DataFrame([Point(0, 0), Point(0, 3), Point(2, 3)])
Out[67]: 
   x  y
0  0  0
1  0  3
2  2  3

Пропущенные данные

Для создания DataFrame с пропущенными данными используется np.nan для представления пропущенных значений. В качестве альтернативы можно передать numpy.MaskedArray в качестве аргумента данных конструктору DataFrame, и его замаскированные элементы будут считаться пропущенными. Подробнее см. Пропущенные данные.

Альтернативные конструкторы

DataFrame.from_dict

DataFrame.from_dict() принимает словарь словарей или словарь массивоподобных последовательностей и возвращает DataFrame. Он работает так же, как и конструктор DataFrame, за исключением параметра orient, который по умолчанию 'columns', но может быть установлен на 'index', чтобы использовать ключи словаря в качестве меток строк.

In [68]: pd.DataFrame.from_dict(dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]))
Out[68]: 
   A  B
0  1  4
1  2  5
2  3  6

Если вы передаете orient='index', ключи будут метками строк. В этом случае вы также можете передать желаемые имена столбцов:

In [69]: pd.DataFrame.from_dict(
   ....:     dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]),
   ....:     orient="index",
   ....:     columns=["one", "two", "three"],
   ....: )
   ....: 
Out[69]: 
   one  two  three
A    1    2      3
B    4    5      6

DataFrame.from_records

DataFrame.from_records() принимает список кортежей или массив со структурированным типом данных. Он работает аналогично обычному конструктору DataFrame, за исключением того, что индекс результирующего DataFrame может быть определенным полем структурированного типа данных.

In [70]: data
Out[70]: 
array([(1, 2., b'Hello'), (2, 3., b'World')],
      dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])

In [71]: pd.DataFrame.from_records(data, index="C")
Out[71]: 
          A    B
C               
b'Hello'  1  2.0
b'World'  2  3.0

Выбор, добавление, удаление столбцов

Вы можете рассматривать DataFrame семантически как словарь индексированных объектов Series. Получение, установка и удаление столбцов работают с тем же синтаксисом, что и аналогичные операции со словарем:

In [72]: df["one"]
Out[72]: 
a    1.0
b    2.0
c    3.0
d    NaN
Name: one, dtype: float64

In [73]: df["three"] = df["one"] * df["two"]

In [74]: df["flag"] = df["one"] > 2

In [75]: df
Out[75]: 
   one  two  three   flag
a  1.0  1.0    1.0  False
b  2.0  2.0    4.0  False
c  3.0  3.0    9.0   True
d  NaN  4.0    NaN  False

Столбцы могут быть удалены или извлечены, как и в словаре:

In [76]: del df["two"]

In [77]: three = df.pop("three")

In [78]: df
Out[78]: 
   one   flag
a  1.0  False
b  2.0  False
c  3.0   True
d  NaN  False

При вставке скалярного значения оно естественным образом будет распространяться для заполнения столбца:

In [79]: df["foo"] = "bar"

In [80]: df
Out[80]: 
   one   flag  foo
a  1.0  False  bar
b  2.0  False  bar
c  3.0   True  bar
d  NaN  False  bar

При вставке Series, который не имеет такого же индекса, как и DataFrame, он будет приведен к индексу DataFrame:

In [81]: df["one_trunc"] = df["one"][:2]

In [82]: df
Out[82]: 
   one   flag  foo  one_trunc
a  1.0  False  bar        1.0
b  2.0  False  bar        2.0
c  3.0   True  bar        NaN
d  NaN  False  bar        NaN

Вы можете вставить исходные массивы, но их длина должна совпадать с длиной индекса DataFrame.

По умолчанию столбцы вставляются в конец. DataFrame.insert() вставляет в определенное место в столбцах:

In [83]: df.insert(1, "bar", df["one"])

In [84]: df
Out[84]: 
   one  bar   flag  foo  one_trunc
a  1.0  1.0  False  bar        1.0
b  2.0  2.0  False  bar        2.0
c  3.0  3.0   True  bar        NaN
d  NaN  NaN  False  bar        NaN

Присвоение новых столбцов в цепочках методов

Вдохновленный методом dplyr’s mutate , DataFrame имеет метод assign(), позволяющий легко создавать новые столбцы, потенциально производные от существующих.

In [85]: iris = pd.read_csv("data/iris.data")

In [86]: iris.head()
Out[86]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name
0          5.1         3.5          1.4         0.2  Iris-setosa
1          4.9         3.0          1.4         0.2  Iris-setosa
2          4.7         3.2          1.3         0.2  Iris-setosa
3          4.6         3.1          1.5         0.2  Iris-setosa
4          5.0         3.6          1.4         0.2  Iris-setosa

In [87]: iris.assign(sepal_ratio=iris["SepalWidth"] / iris["SepalLength"]).head()
Out[87]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

В примере выше мы вставили предварительно вычисленное значение. Мы также можем передать функцию от одного аргумента, которая будет вычисляться для DataFrame, которому она присваивается.

In [88]: iris.assign(sepal_ratio=lambda x: (x["SepalWidth"] / x["SepalLength"])).head()
Out[88]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

assign() всегда возвращает копию данных, оставляя исходный DataFrame неизменным.

Передача вызываемого объекта, а не фактического значения для вставки, полезна, когда у вас нет ссылки на DataFrame. Это распространено при использовании assign() в цепочке операций. Например, мы можем ограничить DataFrame только теми наблюдениями, у которых длина чашелистика больше 5, вычислить отношение и построить график:

In [89]: (
   ....:     iris.query("SepalLength > 5")
   ....:     .assign(
   ....:         SepalRatio=lambda x: x.SepalWidth / x.SepalLength,
   ....:         PetalRatio=lambda x: x.PetalWidth / x.PetalLength,
   ....:     )
   ....:     .plot(kind="scatter", x="SepalRatio", y="PetalRatio")
   ....: )
   ....: 
Out[89]: <Axes: xlabel='SepalRatio', ylabel='PetalRatio'>
../_images/basics_assign.png

Поскольку передается функция, она вычисляется для DataFrame, которому она присваивается. Важно, что это DataFrame, отфильтрованный по строкам с длиной чашелистика больше 5. Сначала происходит фильтрация, а затем вычисления отношения. Это пример, в котором у нас не было доступа к ссылке на отфильтрованный DataFrame.

Подпись функции для assign() проста **kwargs. Ключи — это имена столбцов для новых полей, а значения — либо вставляемое значение (например, Series или массив NumPy), либо функция от одного аргумента, которая будет вызываться для DataFrame. Возвращается копия исходного DataFrame с вставленными новыми значениями.

Порядок **kwargs сохраняется. Это позволяет производить зависимое присвоение, где выражение, расположенное позже в **kwargs, может ссылаться на столбец, созданный ранее в той же операции assign().

In [90]: dfa = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})

In [91]: dfa.assign(C=lambda x: x["A"] + x["B"], D=lambda x: x["A"] + x["C"])
Out[91]: 
   A  B  C   D
0  1  4  5   6
1  2  5  7   9
2  3  6  9  12

Во втором выражении x['C'] будет ссылаться на только что созданный столбец, который равен dfa['A'] + dfa['B'].

Индексирование / выбор

Основы индексирования следующие:

Операция

Синтаксис

Результат

Выбрать столбец

df[col]

Серия

Выбрать строку по метке

df.loc[label]

Серия

Выбрать строку по целочисленной позиции

df.iloc[loc]

Серия

Выбрать срез строк

df[5:10]

DataFrame

Выбрать строки по булевому вектору

df[bool_vec]

DataFrame

Выбор строки, например, возвращает Series, индекс которой представляет собой столбцы DataFrame:

In [92]: df.loc["b"]
Out[92]: 
one            2.0
bar            2.0
flag         False
foo            bar
one_trunc      2.0
Name: b, dtype: object

In [93]: df.iloc[2]
Out[93]: 
one           3.0
bar           3.0
flag         True
foo           bar
one_trunc     NaN
Name: c, dtype: object

Для более подробного изучения сложного индексирования и срезов по меткам см. раздел по индексированию. Мы рассмотрим основы повторного индексирования/приведения к соответствию новых наборов меток в разделе по повторному индексированию.

Выравнивание и арифметические операции с данными

Выравнивание данных между объектами DataFrame автоматически выравнивается по столбцам и индексу (меткам строк). Результирующий объект будет иметь объединение меток столбцов и строк.

In [94]: df = pd.DataFrame(np.random.randn(10, 4), columns=["A", "B", "C", "D"])

In [95]: df2 = pd.DataFrame(np.random.randn(7, 3), columns=["A", "B", "C"])

In [96]: df + df2
Out[96]: 
          A         B         C   D
0  0.045691 -0.014138  1.380871 NaN
1 -0.955398 -1.501007  0.037181 NaN
2 -0.662690  1.534833 -0.859691 NaN
3 -2.452949  1.237274 -0.133712 NaN
4  1.414490  1.951676 -2.320422 NaN
5 -0.494922 -1.649727 -1.084601 NaN
6 -1.047551 -0.748572 -0.805479 NaN
7       NaN       NaN       NaN NaN
8       NaN       NaN       NaN NaN
9       NaN       NaN       NaN NaN

При выполнении операции между DataFrame и Series, по умолчанию выравнивается Series индекс по DataFrame столбцам, таким образом, происходит векторное умножение по строкам. Например:

In [97]: df - df.iloc[0]
Out[97]: 
          A         B         C         D
0  0.000000  0.000000  0.000000  0.000000
1 -1.359261 -0.248717 -0.453372 -1.754659
2  0.253128  0.829678  0.010026 -1.991234
3 -1.311128  0.054325 -1.724913 -1.620544
4  0.573025  1.500742 -0.676070  1.367331
5 -1.741248  0.781993 -1.241620 -2.053136
6 -1.240774 -0.869551 -0.153282  0.000430
7 -0.743894  0.411013 -0.929563 -0.282386
8 -1.194921  1.320690  0.238224 -1.482644
9  2.293786  1.856228  0.773289 -1.446531

Для явного управления поведением соответствия и векторного умножения см. раздел о гибких бинарных операциях.

Арифметические операции со скалярами выполняются поэлементно:

In [98]: df * 5 + 2
Out[98]: 
           A         B         C          D
0   3.359299 -0.124862  4.835102   3.381160
1  -3.437003 -1.368449  2.568242  -5.392133
2   4.624938  4.023526  4.885230  -6.575010
3  -3.196342  0.146766 -3.789461  -4.721559
4   6.224426  7.378849  1.454750  10.217815
5  -5.346940  3.785103 -1.373001  -6.884519
6  -2.844569 -4.472618  4.068691   3.383309
7  -0.360173  1.930201  0.187285   1.969232
8  -2.615303  6.478587  6.026220  -4.032059
9  14.828230  9.156280  8.701544  -3.851494

In [99]: 1 / df
Out[99]: 
          A          B         C           D
0  3.678365  -2.353094  1.763605    3.620145
1 -0.919624  -1.484363  8.799067   -0.676395
2  1.904807   2.470934  1.732964   -0.583090
3 -0.962215  -2.697986 -0.863638   -0.743875
4  1.183593   0.929567 -9.170108    0.608434
5 -0.680555   2.800959 -1.482360   -0.562777
6 -1.032084  -0.772485  2.416988    3.614523
7 -2.118489 -71.634509 -2.758294 -162.507295
8 -1.083352   1.116424  1.241860   -0.828904
9  0.389765   0.698687  0.746097   -0.854483

In [100]: df ** 4
Out[100]: 
           A             B         C             D
0   0.005462  3.261689e-02  0.103370  5.822320e-03
1   1.398165  2.059869e-01  0.000167  4.777482e+00
2   0.075962  2.682596e-02  0.110877  8.650845e+00
3   1.166571  1.887302e-02  1.797515  3.265879e+00
4   0.509555  1.339298e+00  0.000141  7.297019e+00
5   4.661717  1.624699e-02  0.207103  9.969092e+00
6   0.881334  2.808277e+00  0.029302  5.858632e-03
7   0.049647  3.797614e-08  0.017276  1.433866e-09
8   0.725974  6.437005e-01  0.420446  2.118275e+00
9  43.329821  4.196326e+00  3.227153  1.875802e+00

Булевы операторы также работают поэлементно:

In [101]: df1 = pd.DataFrame({"a": [1, 0, 1], "b": [0, 1, 1]}, dtype=bool)

In [102]: df2 = pd.DataFrame({"a": [0, 1, 1], "b": [1, 1, 0]}, dtype=bool)

In [103]: df1 & df2
Out[103]: 
       a      b
0  False  False
1  False   True
2   True  False

In [104]: df1 | df2
Out[104]: 
      a     b
0  True  True
1  True  True
2  True  True

In [105]: df1 ^ df2
Out[105]: 
       a      b
0   True   True
1   True  False
2  False   True

In [106]: -df1
Out[106]: 
       a      b
0  False   True
1   True  False
2  False  False

Транспонирование

Для транспонирования обратитесь к атрибуту T или DataFrame.transpose(), аналогично массивам ndarray:

# only show the first 5 rows
In [107]: df[:5].T
Out[107]: 
          0         1         2         3         4
A  0.271860 -1.087401  0.524988 -1.039268  0.844885
B -0.424972 -0.673690  0.404705 -0.370647  1.075770
C  0.567020  0.113648  0.577046 -1.157892 -0.109050
D  0.276232 -1.478427 -1.715002 -1.344312  1.643563

Взаимодействие DataFrame с функциями NumPy

Большинство функций NumPy могут быть вызваны непосредственно для Series и DataFrame.

In [108]: np.exp(df)
Out[108]: 
           A         B         C         D
0   1.312403  0.653788  1.763006  1.318154
1   0.337092  0.509824  1.120358  0.227996
2   1.690438  1.498861  1.780770  0.179963
3   0.353713  0.690288  0.314148  0.260719
4   2.327710  2.932249  0.896686  5.173571
5   0.230066  1.429065  0.509360  0.169161
6   0.379495  0.274028  1.512461  1.318720
7   0.623732  0.986137  0.695904  0.993865
8   0.397301  2.449092  2.237242  0.299269
9  13.009059  4.183951  3.820223  0.310274

In [109]: np.asarray(df)
Out[109]: 
array([[ 0.2719, -0.425 ,  0.567 ,  0.2762],
       [-1.0874, -0.6737,  0.1136, -1.4784],
       [ 0.525 ,  0.4047,  0.577 , -1.715 ],
       [-1.0393, -0.3706, -1.1579, -1.3443],
       [ 0.8449,  1.0758, -0.109 ,  1.6436],
       [-1.4694,  0.357 , -0.6746, -1.7769],
       [-0.9689, -1.2945,  0.4137,  0.2767],
       [-0.472 , -0.014 , -0.3625, -0.0062],
       [-0.9231,  0.8957,  0.8052, -1.2064],
       [ 2.5656,  1.4313,  1.3403, -1.1703]])

DataFrame не предназначен для прямого замещения ndarray, так как его семантика индексирования и модель данных в некоторых местах сильно отличаются от n-мерного массива.

Series реализует __array_ufunc__, что позволяет ему работать с универсальными функциями NumPy universal functions.

Функция ufunc применяется к базовому массиву в Series.

In [110]: ser = pd.Series([1, 2, 3, 4])

In [111]: np.exp(ser)
Out[111]: 
0     2.718282
1     7.389056
2    20.085537
3    54.598150
dtype: float64

Когда несколько Series передаются в функцию ufunc, они выравниваются перед выполнением операции.

Как и в других частях библиотеки, pandas автоматически выравнивает помеченные входные данные в качестве части ufunc с несколькими входами. Например, используя numpy.remainder() на двух Series с различными порядками меток, будет осуществлено выравнивание перед операцией.

In [112]: ser1 = pd.Series([1, 2, 3], index=["a", "b", "c"])

In [113]: ser2 = pd.Series([1, 3, 5], index=["b", "a", "c"])

In [114]: ser1
Out[114]: 
a    1
b    2
c    3
dtype: int64

In [115]: ser2
Out[115]: 
b    1
a    3
c    5
dtype: int64

In [116]: np.remainder(ser1, ser2)
Out[116]: 
a    1
b    0
c    3
dtype: int64

Как обычно, берется объединение двух индексов, а непересекающиеся значения заполняются пропущенными значениями.

In [117]: ser3 = pd.Series([2, 4, 6], index=["b", "c", "d"])

In [118]: ser3
Out[118]: 
b    2
c    4
d    6
dtype: int64

In [119]: np.remainder(ser1, ser3)
Out[119]: 
a    NaN
b    0.0
c    3.0
d    NaN
dtype: float64

Когда бинарная функция ufunc применяется к Series и Index, реализация Series имеет приоритет, и возвращается Series.

In [120]: ser = pd.Series([1, 2, 3])

In [121]: idx = pd.Index([4, 5, 6])

In [122]: np.maximum(ser, idx)
Out[122]: 
0    4
1    5
2    6
dtype: int64

Функции NumPy ufunc безопасны для применения к Series, поддерживаемым массивами, отличными от ndarray, например, arrays.SparseArray (см. Расчёт с разреженными данными). Если возможно, функция ufunc применяется без преобразования базовых данных в ndarray.

Отображение в консоли

Очень большой DataFrame будет усечён для отображения в консоли. Вы также можете получить сводку, используя info(). (Набор данных baseball взят из пакета plyr R):

In [123]: baseball = pd.read_csv("data/baseball.csv")

In [124]: print(baseball)
       id     player  year  stint team  lg  ...    so  ibb  hbp   sh   sf  gidp
0   88641  womacto01  2006      2  CHN  NL  ...   4.0  0.0  0.0  3.0  0.0   0.0
1   88643  schilcu01  2006      1  BOS  AL  ...   1.0  0.0  0.0  0.0  0.0   0.0
..    ...        ...   ...    ...  ...  ..  ...   ...  ...  ...  ...  ...   ...
98  89533   aloumo01  2007      1  NYN  NL  ...  30.0  5.0  2.0  0.0  3.0  13.0
99  89534  alomasa02  2007      1  NYN  NL  ...   3.0  0.0  0.0  0.0  0.0   0.0

[100 rows x 23 columns]

In [125]: baseball.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100 entries, 0 to 99
Data columns (total 23 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   id      100 non-null    int64  
 1   player  100 non-null    object 
 2   year    100 non-null    int64  
 3   stint   100 non-null    int64  
 4   team    100 non-null    object 
 5   lg      100 non-null    object 
 6   g       100 non-null    int64  
 7   ab      100 non-null    int64  
 8   r       100 non-null    int64  
 9   h       100 non-null    int64  
 10  X2b     100 non-null    int64  
 11  X3b     100 non-null    int64  
 12  hr      100 non-null    int64  
 13  rbi     100 non-null    float64
 14  sb      100 non-null    float64
 15  cs      100 non-null    float64
 16  bb      100 non-null    int64  
 17  so      100 non-null    float64
 18  ibb     100 non-null    float64
 19  hbp     100 non-null    float64
 20  sh      100 non-null    float64
 21  sf      100 non-null    float64
 22  gidp    100 non-null    float64
dtypes: float64(9), int64(11), object(3)
memory usage: 18.1+ KB

Однако, использование DataFrame.to_string() вернёт строковое представление DataFrame в табличной форме, хотя оно не всегда будет помещаться в ширину консоли:

In [126]: print(baseball.iloc[-20:, :12].to_string())
       id     player  year  stint team  lg    g   ab   r    h  X2b  X3b
80  89474  finlest01  2007      1  COL  NL   43   94   9   17    3    0
81  89480  embreal01  2007      1  OAK  AL    4    0   0    0    0    0
82  89481  edmonji01  2007      1  SLN  NL  117  365  39   92   15    2
83  89482  easleda01  2007      1  NYN  NL   76  193  24   54    6    0
84  89489  delgaca01  2007      1  NYN  NL  139  538  71  139   30    0
85  89493  cormirh01  2007      1  CIN  NL    6    0   0    0    0    0
86  89494  coninje01  2007      2  NYN  NL   21   41   2    8    2    0
87  89495  coninje01  2007      1  CIN  NL   80  215  23   57   11    1
88  89497  clemero02  2007      1  NYA  AL    2    2   0    1    0    0
89  89498  claytro01  2007      2  BOS  AL    8    6   1    0    0    0
90  89499  claytro01  2007      1  TOR  AL   69  189  23   48   14    0
91  89501  cirilje01  2007      2  ARI  NL   28   40   6    8    4    0
92  89502  cirilje01  2007      1  MIN  AL   50  153  18   40    9    2
93  89521  bondsba01  2007      1  SFN  NL  126  340  75   94   14    0
94  89523  biggicr01  2007      1  HOU  NL  141  517  68  130   31    3
95  89525  benitar01  2007      2  FLO  NL   34    0   0    0    0    0
96  89526  benitar01  2007      1  SFN  NL   19    0   0    0    0    0
97  89530  ausmubr01  2007      1  HOU  NL  117  349  38   82   16    3
98  89533   aloumo01  2007      1  NYN  NL   87  328  51  112   19    1
99  89534  alomasa02  2007      1  NYN  NL    8   22   1    3    1    0

Широкие DataFrame по умолчанию будут выводиться по нескольким строкам:

In [127]: pd.DataFrame(np.random.randn(3, 12))
Out[127]: 
         0         1         2   ...        9         10        11
0 -1.226825  0.769804 -1.281247  ... -1.110336 -0.619976  0.149748
1 -0.732339  0.687738  0.176444  ...  1.462696 -1.743161 -0.826591
2 -0.345352  1.314232  0.690579  ...  0.896171 -0.487602 -0.082240

[3 rows x 12 columns]

Можно изменить количество выводимого текста на одной строке, задав опцию display.width:

In [128]: pd.set_option("display.width", 40)  # default is 80

In [129]: pd.DataFrame(np.random.randn(3, 12))
Out[129]: 
         0         1         2   ...        9         10        11
0 -2.182937  0.380396  0.084844  ... -0.023688  2.410179  1.450520
1  0.206053 -0.251905 -2.213588  ... -0.025747 -0.988387  0.094055
2  1.262731  1.289997  0.082423  ... -0.281461  0.030711  0.109121

[3 rows x 12 columns]

Можно настроить максимальную ширину отдельных столбцов, задав display.max_colwidth

In [130]: datafile = {
   .....:     "filename": ["filename_01", "filename_02"],
   .....:     "path": [
   .....:         "media/user_name/storage/folder_01/filename_01",
   .....:         "media/user_name/storage/folder_02/filename_02",
   .....:     ],
   .....: }
   .....: 

In [131]: pd.set_option("display.max_colwidth", 30)

In [132]: pd.DataFrame(datafile)
Out[132]: 
      filename                           path
0  filename_01  media/user_name/storage/fo...
1  filename_02  media/user_name/storage/fo...

In [133]: pd.set_option("display.max_colwidth", 100)

In [134]: pd.DataFrame(datafile)
Out[134]: 
      filename                                           path
0  filename_01  media/user_name/storage/folder_01/filename_01
1  filename_02  media/user_name/storage/folder_02/filename_02

Также можно отключить эту функцию с помощью опции expand_frame_repr. Это выведет таблицу в одном блоке.

Доступ к столбцам DataFrame по атрибутам и автодополнение в IPython

Если метка столбца DataFrame является допустимым именем переменной Python, к столбцу можно получить доступ как к атрибуту:

In [135]: df = pd.DataFrame({"foo1": np.random.randn(5), "foo2": np.random.randn(5)})

In [136]: df
Out[136]: 
       foo1      foo2
0  1.126203  0.781836
1 -0.977349 -1.071357
2  1.474071  0.441153
3 -0.064034  2.353925
4 -1.282782  0.583787

In [137]: df.foo1
Out[137]: 
0    1.126203
1   -0.977349
2    1.474071
3   -0.064034
4   -1.282782
Name: foo1, dtype: float64

Столбцы также связаны с механизмом автодополнения IPython, поэтому они могут быть дополнены нажатием клавиши Tab:

In [5]: df.foo<TAB>  # noqa: E225, E999
df.foo1  df.foo2

© 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/dsintro.html

Spec-Zone.ru

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