REF: Шпаргалка по Pandas
Создание таблиц
В pandas есть два основных объекта: DataFrame (таблица) и Series (последовательность значений, например, один столбец DataFrame). У таблицы есть ключи (индексы) строчек - index и колонок - columns. По умолчанию index целочисленный. Индексы строк не обязаны быть уникальными. При их совпадении, обращение к индексу df.loc[row] вернёт таблицу с несколькими строками.
Принципиальная разница с массивами numpy в том, что колонки pandas DataFrame могут быть различного типа (в том числе экземплярами класса). Таблицу можно транспонировать df.T и в этом смысле особой разницы между строками и колонками нет. Но если колонки различного типа транспонирование превратит все типы в object.
import pandas as pd
df = pd.DataFrame() # создаём пустую таблицу
print(df.index, df.columns) # Index( [] ) Index([])
df = pd.DataFrame([1,3,2], # одна колонка из 3-х чисел с именем A
columns=['A']) # индексы строчек по умолчанию [0,1,2]
print(df.index) # RangeIndex(start=0,stop=3,step=1)
print(df.columns) # Index(['A'])
df = pd.DataFrame() # тоже самое
df['A'] = [1,2,3] # создаём колонку в пустой таблице
df = pd.DataFrame([[1,2], # таблица из 2D массива
[3,4]]) # первая строка [1,2], вторая [3,4]
Таблицу можно задать при помощи словаря (его ключи - имена колонок). Если у ключа скалярное значение - оно будет продублировано. При задании ключей строчек (index) можно смешивать str, int, Timestamp и т.п.:
cat = pd.Categorical(["a","b","a"])} # категориальные (не строки!)
df = pd.DataFrame(
{"Num": 1, # | Num Name Age Cat
"Name": ["Jon", "Mia", "Sam"], # --------------------
"Age" : [43, np.nan, 56], # m | 1 Jon 43 a
"Cat": cat, # f | 1 Mia NaN b
index=['m', 'f', 0]) # 0 | 1 Sam 56 a
Список словарей - это список строк. Каждый новый ключ порождает колонку:
# | b c a
pd.DataFrame([{ 'b': 1, 'c': 2}, # ----------------
{'a': 3, 'b': 4, 'c': 5}]) # 0 | 1 2 NaN
# 1 | 3 4 5
df1 = df.copy() # deepcopy таблицы
Работа с файлами
При загрузке из csv-файла можно (index_col) задать некоторую колонку (по имени или по номеру) как индексную (имена строчек):
df = pd.read_csv("name.csv", index_col="id")
Чтение больших файлов по кускам:
chunksize = 10 ** 6
with pd.read_csv(filename, chunksize=chunksize) as reader:
for chunk in reader:
process(chunk)
Сохранение в csv-файл:
df.to_csv("submission.csv", index=False) # без сохранения индекса
df.to_csv('probs.zip', index=False, # запаковать в zip-файл
compression=dict(method='zip', archive_name='probs.csv') )
Если index=False не указывать, в csv добавиться первая безымянная колонка
с индексами.
Сводная информация
print('shape:', df.shape) # вывод формы (rows, cols)
display(df) # распечатать (если не последняя команда)
df.head(7) # 7 первых строк (5 по умолчанию)
df.tail(2) # 2 последние строки (5 по умолчанию)
df.sample(2) # 2 случайные строки
df.describe() # статистики по всем колонкам
df.col.describe() # статистики колонке c именем 'col'
df.describe(include=[bool])) # только логические колонки (bool,float,object,category)
df.col.mean() # среднее значение по колонке 'col'
df.col.unique() # список уникальных значений
df.col.value_counts() # таблица имя значения - число раз в 'col'
df.info() # список колонок с типами и непустых данных
df.duplicated().sum() # число дубликатов строк
df.isna().sum() # вывод числа пропусков в каждой колонке
Формат для вывода describe и т.п.:
pd.set_option('display.float_format', lambda x: '%.2f' % x)
Доступ к элементам
Метод at даёт доступ к элементам таблицы, а метод loc - к срезам по индексам (строк и колонок). Тоже самое для iat, iloc, но индексация при помощи номеров строк и колонок (целые числа, срезы подобно numpy). Для loc срезы включают границы, а для iloc срезы понимаются в обычном для Python смысле (верхняя граница не включена).
Свойство at[rowID, colID] быстрее, чем loc[rowID, colID] для одного элемена:df.at[0,'a'] = 5Свойство loc позволяет извлечь одну строку, список строк или их срез, а также срез таблицы (index, columns). При этом в срезе последний индекс включается (!):
df.loc[ 0 ] # Series (0-я строка) df.loc[ 0 ] = 5 # присвоить всем элементам строки 0 значение 5 df.loc[ [1,3] ] # DataFrame с 2-я строками df.loc[ :2] # DataFrame c 3-я строками df.loc[0, 'a'] # аналогично at, но медленнее df.loc[1:3, 'a'] # результат - Series (3 элемента) df.a.loc[1:3] # тоже самое (можно и со строковыми индексами) df.loc[1:3, ['a','b']] # DataFrame (срез 3 строки, 2 колонки) df.loc[[1,3], ['a','b']] # DataFrame (вырез 2 строки, 2 колонки)
Получение колонок: df['col_name'] - это даёт Series. Тот-же эффект: df.col_name (если колонка уже существует). Можно список имён колонок: df[['a','c']] - тогда это DataFrame, а не Series.
df['a'] = 5 # изменить все значения в колонке 5 df.a = 5 # тоже самое df['a'] = [1,2,3] # элементы колонки (должна совпадать с другими колонками)
Логическая индексация
df.loc[ df.a > 1 ] # DataFrame в которой значения в кллонке a > 1 df.loc[ (df.a > 1) & (df.a < 5) ] df.loc[df.c.isin(['aa','bb'])] # присутствуют значения в колонке 'c' df.loc[df.isin(['aa','bb'])] # присутствуют значения в любой колонке df.loc[lambda df: df.a == 8] # отобрать строки, удовлетворящие функции df[df > 0] # все элементы большие нуля (остальные NaN) df[df > 0] = -df
Изменение таблиц
Сортировка:
df = df.sort_values(by="A") # сортировки по колонкам df = df.sort_values(by=["A", "B"]) df = df.sort_index(axis=1, ascending=False) # сортировка колонок df = df.sort_index(axis=0) # сортировка индексов строк (один типа!)
Переименование колонок и индексов строк:
df.rename(columns = {'points': 'score'})
df.rename(index = {0: 'firstEntry', 1: 'secondEntry'})
Добавление строк и колонок
Добавляем строку при помощи loc или конкатенацией concat таблиц:
df = pd.DataFrame({'a':[1,2], 'b':[-1,-2]}) # | a b
# ----------
df.loc[len(df.index)] = [3, -3] # 0 | 1 -1
# 1 | 2 -2
da = pd.DataFrame([{'a':4, 'b':'-4'}]) # 2 | 3 -3
df = pd.concat([df, da]) # 3 | 4 -4
df = df.reset_index() # сделать снова индексы строк уникальными (!)
Добавление колонки со значением длины строки в колонке "name"
df["len"] = df["name"].map(lambda x: len(x))
Неопределённые ячейки
Некоторые ячейки могут быть неопределёнными (None или np.nan), что при выводе отражается как NaN. Метод isna возвращает DataFrame с логическими значениями (его антипод notna):
df = pd.DataFrame({'a':[1,2], 'b':[3,None]}) # | a b
# ---------------
# 0 | False False
df.isna() # 1 | False True
Можно создавать таблицу с неопределёнными ячейками (для порождения больших таблиц эффективнее создать сначала неопределённую, и затем её заполнять, чем добавлять строки). Значениями в ячейках могут быть объекты (списки и т.п.).
df = pd.DataFrame(index=range(2), # | a
columns=['a']) # --------
df.at[0,'a'] = [1,2] # 0 | [1,2]
df # 1 | NaN
Но после сохранения в файл они будут json-строками и после чтения файла их надо будет снова парсить.
df.dropna(how="any") # выкинуть все строки с пропусками df.fillna(value=5) # заполнить одним значением
Вычисления в таблицах
df['c']= df.a + df.b # создаём новую колонку или меняем существующую df.a = df.a.map(lambda x: x - 50) # меняем колонку при помощи функции
| count | количество не-NA объектов |
|---|---|
| sum | cумма |
| mean | cреднее значение |
| mad | cреднее абсолютное отклонение |
| median | медиана |
| min | минимум |
| max | максимум |
| mode | мода |
| abs | абсолютное значение |
| prod | произведение |
| std | стандартное отклонение |
| var | несмещенная дисперсия |
| sem | стандартная ошибка среднего |
| skew | скошенность (момент 3-го порядка) |
| kurt | эксцесс (момент 4-го порядка) |
| quantile | квантиль (%) |
| cumsum | кумулятивная сумма |
| cumprod | кумулятивное произведение |
| cummax | кумулятивный максимум |
| cummin | кумулятивный минимум |
Агрегированная информация
Вместо стандартной describe, можно самостоятельно формировать итоговые таблицы по колонкам (текстовые имена для функций панды df.sum и т.п.), иначе просто имя функции:
def fun(x):
return sum(x)**0.5
fun.__name__ = 'sqrt' # можно для вывода любое имя
df.agg(['sum', 'mean', 'std', max, strange])
Группы
df = pd.DataFrame({
'A': ['a','b','a','b','b'],
'B': ['0','1','0','1','0'],
'X': [ 1, 2, 3, 4, 5 ],
'Y': [ 0, 1, 1, 0, 0 ]
} )
Сгруппируем одинаковые значения в колонке A
и пробежимся по всем группам (каждая группа - это DataFrame):
groups = df.groupby("A") # val=a
# | A B X Y
for val_A, df_A in groups: # --------------
print("val=",val_A) # 0 | a 0 1 0
display(df_A) # 2 | a 0 3 1
#...
Получим сумму по всем числовым колонкам в каждой группе (индекс получил имя index.name='A'):
# | X Y
df.groupby("A").sum() # A -------
# a | 4 1
# b | 11 1
df.groupby("A").X.sum() # получить Series сумм в группах для колонки X
Сначала сгруппировать по A, затем по B:
# | X Y
df.groupby(["A","B"]).sum() # A B |-----
# a 0 | 4 1
# b 0 | 5 0
# 1 6 | 1
Теперь индекс - это не одно число а пара чисел:MultiIndex([('a', '0'), ('b', '0'), ('b', '1')], names=['A', 'B'])
Агрегация групп
df = pd.DataFrame({'s': [0,0,0,1,1,2],
'i': [1,2,1,1,3,2],
't': [0,0,1,0,1,0]})
Создание таблицы в которой колонка sess уникальна, а все aid собраны в множество
(второй пример - с переименованием колонки "set" → "s1" "min" → "mi"):
df.groupby('s').i.agg([set, min]) # s set min
# 0 {1,2} 1
df.groupby('s').agg(s1=('i',set), mi=('i',min)) # 1 {1,3} 1
# 2 {2} 2
После группировки индекс таблицы преобретает имя index.name == s.
gr0 = df[df.t==0].groupby('s').agg(s1=('i',set)) # s s1 s2
gr1 = df[df.t==1].groupby('s').agg(s2=('i',set)) # 0 {1,2} {1}
# 1 {1} {3}
pd.concat([gr0, gr1], axis=1) # 2 {2} NaN
Использование справочников
Связывание таблиц подобно SQL (df_ref - таблица справочник даёт свои значения в таблицу df по ключу i):
df_ref = pd.DataFrame({'i':[0, 1, 3, 4], # | i v s
'v':[10,20,30,40], # ----------------
's':['a','b','c','d']}) # 0 | 0 10 a
# 1 | 0 10 a
df = pd.DataFrame({"i":[0,0,1,3,2] }) # 2 | 1 20 b
# 3 | 3 30 c
df.merge(df_ref, on='i', how="left") # 4 | 2 NaN NaN
То-же если имена колонок для коннекта различные
df.merge(df_ref, left_on='i1', right_on='i', how="left")
Работа со строковыми данными
Через атрибут str получаем доступ ко всем методам типа str применительно к Series:s.str.lower() s.str.lower().str.strip()
Работа с временем
Объекты времени и периода времени
ts = pd.Timestamp('2022-11-13 00:00:00', tz=None)
ts.to_pydatetime() # datetime.datetime(2022, 11, 13, 0, 0)
rng = pd.date_range('2022-11-13', periods=3, freq='D')
rng.to_pydatetime() # list 3-х datetime
Если дата-время в csv сохранена Unix timestamp (seconds passed since 1970-1-1)
то их можно конвертировать:
df = pd.DataFrame({"date": [1659304800, # 2022-07-31 22:00:00
1659304904]}) # 2022-07-31 22:01:44
df.date = pd.to_datetime(df.date, unit='s') # D,s,ms,us,ns
t1 = pd.to_datetime(df.ts.min(), unit='s') t2 = pd.to_datetime(df.ts.max(), unit='s') print(t1, t2, pd.Timedelta(t2 - t1) )
df.time = pd.to_datetime(df.date, unit='s') # D,s,ms,us,ns df['hour'] = df.time.dt.hour # номер часа df['weekday'] = df.time.dt.weekday # номер дня недели