Spec-Zone.ru › pandas 2

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

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

In [1]: import numpy as np

In [2]: import pandas as pd

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

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

Series

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

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

Здесь data может представлять собой несколько разных типов объектов:

  • словарь Python

  • ndarray

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

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

Из ndarray

Если data — это ndarray, длина index должна совпадать с длиной data. Если индекс не передан, он будет создан автоматически со значениями [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

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

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 — это скалярное значение, необходимо указать индекс. Значение будет повторено столько раз, сколько элементов в index.

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

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

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

Хотя 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() вернёт ndarray NumPy.

Series подобен словарю

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

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

Атрибут 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'

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

Переименовать объект Series можно с помощью метода pandas.Series.rename().

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

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

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

DataFrame

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

  • Dict из одномерных ndarray, списков, dict или Series

  • 2-D numpy.ndarray

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

  • Объект Series

  • Другой объект DataFrame

Помимо данных, можно передать аргументы index (метки строк) и columns (метки столбцов). Если передать index и / или columns, будут заданы index и / или columns результирующего DataFrame. Поэтому при передаче dict из Series и заданного index все данные, не соответствующие переданному index, будут отброшены.

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

Из dict объектов Series или dict

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

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

К меткам строк и столбцов можно обратиться соответственно через атрибуты index и columns:

Примечание

Если вместе с dict данных передан определённый набор столбцов, переданные столбцы имеют приоритет над ключами dict.

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

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

Из dict ndarray / списков

Все ndarray должны иметь одинаковую длину. Если передан index, его длина также должна совпадать с длиной массивов. Если index не передан, результатом будет 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

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

Этот случай обрабатывается так же, как dict массивов.

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 ndarray.

Из списка dict

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

Из dict кортежей

Передав dict кортежей, можно автоматически создать фрейм с MultiIndex.

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 с тем же index, что и у входного 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

Из списка классов данных

Классы данных, представленные в PEP557, можно передавать конструктору DataFrame. Передача списка классов данных эквивалентна передаче списка словарей.

Обратите внимание: все значения в списке должны быть классами данных; смешение типов в списке приведёт к 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 в качестве аргумента data конструктору DataFrame; его замаскированные элементы будут считаться пропущенными. Подробнее см. раздел Пропущенные данные.

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

DataFrame.from_dict

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

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() принимает список кортежей или ndarray со структурированным dtype. Он работает аналогично обычному конструктору DataFrame, за исключением того, что индекс результирующего DataFrame может соответствовать определённому полю структурированного dtype.

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 можно работать как с dict объектов Series с одинаковыми индексами. Получение, установка и удаление столбцов выполняются с использованием того же синтаксиса, что и соответствующие операции с dict:

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

Столбцы можно удалять или извлекать с помощью pop, как и в dict:

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

Можно вставлять исходные ndarray, но их длина должна совпадать с длиной индекса 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

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

Вдохновлённый глаголом mutate из dplyr, 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]

Series

Выбор строки по метке

df.loc[label]

Series

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

df.iloc[loc]

Series

Срез строк

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 по умолчанию index объекта Series выравнивается по columns объекта 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.

Универсальная функция применяется к базовому массиву в объекте 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, перед выполнением операции они выравниваются.

Как и в других частях библиотеки, pandas автоматически выравнивает входные данные с метками при использовании универсальной функции с несколькими аргументами. Например, при использовании 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

Если бинарная универсальная функция применяется к объекту 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 можно безопасно применять к объектам Series, основанным на массивах, отличных от ndarray, например arrays.SparseArray (см. раздел Вычисления с разреженными данными). Если возможно, универсальная функция применяется без преобразования базовых данных в ndarray.

Вывод в консоль

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

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

Spec-Zone.ru

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