Таблицы pandas
Contents
Таблицы 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
— использовать словари.
Тут возможно два варианта.
ключи словаря — названия столбца, значение по ключу — содержимое соответствующего столбца;
ключи словаря — метки строк, значение по ключу — содержимое соответствующей строки.
По столбцам#
Первый вариант гораздо более распространен, поэтому его рассмотрим первым. Итак, ключи словаря станут названиями столбцов, значение по ключу — станет содержимым с соответствующим значением.
Будущие столбцы в словари могут быть представлены списком, массивом 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 |