Таблицы pandas#

pandas.DataFrame — по сути дела таблица, на которую можно смотреть как на объединение столбцов pandas.Series с выравниванием по общему индексу.

import pandas as pd

s1 = pd.Series({
    "a": 1,
    "b": 2
}, dtype="Int8")
s2 = pd.Series({
    "b": "two",
    "c": "three"
})

df = pd.DataFrame({
    "s1": s1,
    "s2": s2
})

df
s1 s2
a 1 NaN
b 2 two
c <NA> three

Таблицы изменяемы с точки зрения содержимого их ячеек, но лишь частично изменяемы с точки зрения размера: добавлять на месте можно только столбцы, но не строки.

Создание таблицы#

Как и в случае со столбцами, есть множество способов создать таблицу pandas из уже существующих объектов python. Большинство из них опираются на конструктор pandas.DataFrame.

Список списков или двухмерный массив NumPy#

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

data = [
    ["a11", "a12", "a13"],
    ["a21", "a22", "a23"]
    ]

df = pd.DataFrame(data)
df
0 1 2
0 a11 a12 a13
1 a21 a22 a23

По умолчанию генерируется RangeIndex и для строк и для столбцов таблицы.

print(f"{df.index=}, {df.columns=}")
df.index=RangeIndex(start=0, stop=2, step=1), df.columns=RangeIndex(start=0, stop=3, step=1)

Опциональными параметрами конструктора columns и index можно указать пользовательские значения.

df = pd.DataFrame(data, columns=["column 1", "column 2", "column 3"], index=["row 1", "row 2"])
df
column 1 column 2 column 3
row 1 a11 a12 a13
row 2 a21 a22 a23

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

import numpy as np

data = np.array(data)
df = pd.DataFrame(data, columns=["column 1", "column 2", "column 3"], index=["row 1", "row 2"])
df
column 1 column 2 column 3
row 1 a11 a12 a13
row 2 a21 a22 a23

Словарь#

Один самых удобных способов создавать таблицу в pandas — использовать словари.

Тут возможно два варианта.

  1. ключи словаря — названия столбца, значение по ключу — содержимое соответствующего столбца;

  2. ключи словаря — метки строк, значение по ключу — содержимое соответствующей строки.

По столбцам#

Первый вариант гораздо более распространен, поэтому его рассмотрим первым. Итак, ключи словаря станут названиями столбцов, значение по ключу — станет содержимым с соответствующим значением.

Будущие столбцы в словари могут быть представлены списком, массивом NumPy, а также столбцом pandas. При этом в случае списков и массивов NumPy накладывается требование на одинаковую длину всех столбцов, а также автоматически генерируется RangeIndex, если он не указан в явном виде опциональным параметром index.

col1 = np.array(["a11", "a21"])
col2 = ["a21", "a22"]
col3 = "a31", "a32"

d = {
    "column 1": col1,
    "column 2": col2,
    "column 3": col3
}

df = pd.DataFrame(d, index=["a", "b"])
df
column 1 column 2 column 3
a a11 a21 a31
b a21 a22 a32

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

import pandas as pd
import numpy as np

col1 = pd.Series([1, 2], index=["a", "b"])
col2 = pd.Series([3, 4], index=["b", "a"], dtype="Int64")
col3 = pd.Series([5, 6, 7], index=["a", "b", "c"])

d = {
    'column 1': col1, 
    'column 2': col2,
    'column 3': col3,
}

df = pd.DataFrame(d)
df
column 1 column 2 column 3
a 1.0 4 5
b 2.0 3 6
c NaN <NA> 7

По строкам#

Статический метод pandas.DataFrame.form_dict — более специализированный метод для создания таблицы из словаря. В примерах из предыдущего раздела этот метод сработает точно также, как и базовый конструктор класса, но наличие дополнительного опционального параметра orient (orientation) позволяет создавать таблицу из строк.

Если указать в качестве orient строку index, то ключи словаря будут восприниматься в качестве меток строк, а значение по ключу — содержимое строки с соответствующей меткой. Все остальное продолжает работать также, но с заменой меток и названий столбцов местами.

row1 = pd.Series([1, 2], index=["column 1", "column 2"])
row2 = pd.Series([3, 4], index=["column 2", "column 1"])

d = {
    "row1": row1,
    "row2": row2,
}

pd.DataFrame.from_dict(d, orient="index")
column 1 column 2
row1 1 2
row2 4 3

Чтение таблиц с жесткого диска#

Библиотека pandas позволяет свободно оперировать с таблицами в формате csv, json, таблицами excel (потребуется установка дополнительной библиотеки, например, openpyxl), а также более продвинутыми бинарными форматами hdf5, apache parquet и многими другими форматами. Формат csv — один из самых простых и распространенных в научной среде, поэтому рассмотрим чтение таблиц средствами pandas именно на его примере.

Note

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

Предположим следующее содержимое хранится в текстовом файле planets.csv со следующим содержимым.

Название,Количество спутников,Масса,Группа,Кольца
Меркурий,0,0.0055,земная группа,Нет
Венера,0,0.815,земная группа,Нет
Земля,1,1.0,земная группа,Нет
Марс,2,0.107,земная группа,Нет
Юпитер,62,317.8,газовый гигант,Да 
Сатурн,34,95.2,газовый гигант,Да
Уран,27,14.37,ледяной гигант,Да
Нептун,13,17.15,ледяной гигант,Да

Для чтения такой таблицы используется метод read_csv.

import os
path = os.path.join("..", "..", "assets", "data", "tables", "planets.csv")

planets = pd.read_csv(path)
print(planets.info())
planets.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 5 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   Название              8 non-null      object 
 1   Количество спутников  8 non-null      int64  
 2   Масса                 8 non-null      float64
 3   Группа                8 non-null      object 
 4   Кольца                8 non-null      object 
dtypes: float64(1), int64(1), object(3)
memory usage: 448.0+ bytes
None
Название Количество спутников Масса Группа Кольца
0 Меркурий 0 0.0055 земная группа Нет
1 Венера 0 0.8150 земная группа Нет
2 Земля 1 1.0000 земная группа Нет
3 Марс 2 0.1070 земная группа Нет
4 Юпитер 62 317.8000 газовый гигант Да

В самом простом варианте использования функции read_csv

  • имена столбцов распознаются из первой строки файла (параметром header можно повлиять на это);

  • в качестве индекса генерируется RangeIndex (параметром index_col можно выбрать столбец индекса таблицы);

  • в качестве разделителя ожидается символ запятой “,” (параметром sep можно на это повлиять);

  • пропущенные значения заполняются значением “np.nan” (параметром na_values можно указать, какие ещё значения интерпретировать, как пропущенные);

  • столбцы с датами не распознаются (смотри страницу “Дата и время”).

Note

Метод DataFrame.head возвращает первые n строк таблицы. По умолчанию n равно 5, но можно указать явно и другое значение. Похожий по смыслу метод DataFrame.tail возвращает последние n строк.

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

Считаем эту таблицу ещё раз, указав в этот раз в качестве индекса столбец "Название".

planets = pd.read_csv(path, index_col="Название", sep=",")
planets
Количество спутников Масса Группа Кольца
Название
Меркурий 0 0.0055 земная группа Нет
Венера 0 0.8150 земная группа Нет
Земля 1 1.0000 земная группа Нет
Марс 2 0.1070 земная группа Нет
Юпитер 62 317.8000 газовый гигант Да
Сатурн 34 95.2000 газовый гигант Да
Уран 27 14.3700 ледяной гигант Да
Нептун 13 17.1500 ледяной гигант Да

Аналогично можно считывать данные из таблиц excel методом read_excel.

Методами to_csv и to_excel можно сохранить DataFrame в таблицу удобном формате (для сохранения в excel необходимо поставить библиотеку openpyxl или её аналоги).

Индексация#

Строки#

Для получения строк таблицы используются те же самые .loc и iloc (метки и порядковый номер соответственно).

print(planets.loc["Марс"])
print("_" * 80)
print(planets.iloc[2])
Количество спутников                2
Масса                           0.107
Группа                  земная группа
Кольца                            Нет
Name: Марс, dtype: object
________________________________________________________________________________
Количество спутников                1
Масса                             1.0
Группа                  земная группа
Кольца                            Нет
Name: Земля, dtype: object

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

Warning

Простые квадратные скобки “[]” не индексируют таблицу по строкам!

Столбцы#

Для получения столбца используется оператор “[]”.

planets["Количество спутников"]
Название
Меркурий     0
Венера       0
Земля        1
Марс         2
Юпитер      62
Сатурн      34
Уран        27
Нептун      13
Name: Количество спутников, dtype: int64

Если в названии столбца нет пробелов и оно не совпадает ни с одним методом класса pandas.DataFrame, то можно использовать точечную нотацию. Хотя, конечно, в случае кириллицы это выглядит странно.

planets.Масса
Название
Меркурий      0.0055
Венера        0.8150
Земля         1.0000
Марс          0.1070
Юпитер      317.8000
Сатурн       95.2000
Уран         14.3700
Нептун       17.1500
Name: Масса, dtype: float64

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

planets[["Группа", "Кольца", "Масса"]]
Группа Кольца Масса
Название
Меркурий земная группа Нет 0.0055
Венера земная группа Нет 0.8150
Земля земная группа Нет 1.0000
Марс земная группа Нет 0.1070
Юпитер газовый гигант Да 317.8000
Сатурн газовый гигант Да 95.2000
Уран ледяной гигант Да 14.3700
Нептун ледяной гигант Да 17.1500

Конкретные ячейки#

Для получения доступа сразу к конкретной ячейке используются методы DataFrame.at и DataFrame.iat.

  • метод DataFrame.at принимает на вход метку строки и название столбца, и возвращает значение ячейки, располагающейся на их пересечении.

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

print(f"{planets.at['Меркурий', 'Количество спутников']=}, {planets.iat[0, 0]=}")
planets.at['Меркурий', 'Количество спутников']=0, planets.iat[0, 0]=0

Однако, если метки строк и названия столбцов повторяются, то методом “.at” вместо значения одной ячейки вы можете получить или сразу pandas.Series или pandas.DataFrame.

duplicated_df = pd.DataFrame(data=[[1, 1], [1, 1]], index=["a", "a"], columns=["b", "b"])
duplicated_df
b b
a 1 1
a 1 1
duplicated_df.at["a", "b"]
b b
a 1 1
a 1 1

Добавление столбцов#

Добавление и изменение столбцов в таблицу похоже на добавление элементов в словарь. При этом данные автоматически выравниваются по индексу. В качестве примера добавим к таблице про планет столбец с данными про экваториальный диаметр. Обратите внимание, что планеты перечислены в порядке отличном от порядка таблицы, и далеко не все планеты есть в новом столбце. В итоговой таблицы все присутствующие значения выравниваются, а недостающие заменяются на NaN.

planets["Экваториальный диаметр"] = pd.Series({
    "Венера": 0.949,
    "Сатурн": 9.449,
    "Земля": 1.0,
    "Меркурий": 0.382,
})

planets
Количество спутников Масса Группа Кольца Экваториальный диаметр
Название
Меркурий 0 0.0055 земная группа Нет 0.382
Венера 0 0.8150 земная группа Нет 0.949
Земля 1 1.0000 земная группа Нет 1.000
Марс 2 0.1070 земная группа Нет NaN
Юпитер 62 317.8000 газовый гигант Да NaN
Сатурн 34 95.2000 газовый гигант Да 9.449
Уран 27 14.3700 ледяной гигант Да NaN
Нептун 13 17.1500 ледяной гигант Да NaN