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)
df = pd.DataFrame([ 1 , 3 , 2 ],
columns = [ 'A' ])
print (df.index)
print (df.columns)
df = pd.DataFrame()
df[ 'A' ] = [ 1 , 2 , 3 ]
df = pd.DataFrame([[ 1 , 2 ],
[ 3 , 4 ]])
|
Таблицу можно задать при помощи словаря (его ключи - имена колонок).
Если у ключа скалярное значение - оно будет продублировано.
При задании ключей строчек (index) можно смешивать
str, int, Timestamp и т.п.:
cat = pd.Categorical([ "a" , "b" , "a" ])}
df = pd.DataFrame(
{ "Num" : 1 ,
"Name" : [ "Jon" , "Mia" , "Sam" ],
"Age" : [ 43 , np.nan, 56 ],
"Cat" : cat,
index = [ 'm' , 'f' , 0 ])
|
Список словарей - это список строк. Каждый новый ключ порождает колонку:
pd.DataFrame([{ 'b' : 1 , 'c' : 2 },
{ 'a' : 3 , 'b' : 4 , 'c' : 5 }])
|
Работа с файлами
При загрузке из 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 ,
compression = dict (method = 'zip' , archive_name = 'probs.csv' ) )
|
Если
index=False не указывать, в csv добавиться первая безымянная колонка
с индексами.
Сводная информация
print ( 'shape:' , df.shape)
display(df)
df.head( 7 )
df.tail( 2 )
df.sample( 2 )
df.describe()
df.col.describe()
df.describe(include = [ bool ]))
df.col.mean()
df.col.unique()
df.col.value_counts()
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] для одного элемена:
Свойство
loc позволяет извлечь одну строку, список строк или их срез,
а также срез таблицы (
index, columns).
При этом в срезе
последний индекс включается (!):
df.loc[ 0 ]
df.loc[ 0 ] = 5
df.loc[ [ 1 , 3 ] ]
df.loc[ : 2 ]
df.loc[ 0 , 'a' ]
df.loc[ 1 : 3 , 'a' ]
df.a.loc[ 1 : 3 ]
df.loc[ 1 : 3 , [ 'a' , 'b' ]]
df.loc[[ 1 , 3 ], [ 'a' , 'b' ]]
|
Получение колонок: df['col_name'] - это даёт Series.
Тот-же эффект: df.col_name (если колонка уже существует).
Можно список имён колонок: df[['a','c']] - тогда это DataFrame, а не Series.
df[ 'a' ] = 5
df.a = 5
df[ 'a' ] = [ 1 , 2 , 3 ]
|
Логическая индексация
df.loc[ df.a > 1 ]
df.loc[ (df.a > 1 ) & (df.a < 5 ) ]
df.loc[df.c.isin([ 'aa' , 'bb' ])]
df.loc[df.isin([ 'aa' , 'bb' ])]
df.loc[ lambda df: df.a = = 8 ]
df[df > 0 ]
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 ]})
df.loc[ len (df.index)] = [ 3 , - 3 ]
da = pd.DataFrame([{ 'a' : 4 , 'b' : '-4' }])
df = pd.concat([df, da])
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 ]})
df.isna()
|
Можно создавать таблицу с неопределёнными ячейками (для порождения больших таблиц эффективнее создать
сначала неопределённую, и затем её заполнять, чем добавлять строки).
Значениями в ячейках могут быть объекты (списки и т.п.).
df = pd.DataFrame(index = range ( 2 ),
columns = [ 'a' ])
df.at[ 0 , 'a' ] = [ 1 , 2 ]
df
|
Но после сохранения в файл они будут 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" )
for val_A, df_A in groups:
print ( "val=" ,val_A)
display(df_A)
|
Получим сумму по всем числовым колонкам в каждой группе (индекс получил имя
index.name='A'):
df.groupby( "A" ). sum ()
df.groupby( "A" ).X. sum ()
|
Сначала сгруппировать по A, затем по B:
df.groupby([ "A" , "B" ]). sum ()
|
Теперь индекс - это не одно число а пара чисел:
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 ])
df.groupby( 's' ).agg(s1 = ( 'i' , set ), mi = ( 'i' , min ))
|
После группировки индекс таблицы преобретает имя index.name == s.
gr0 = df[df.t = = 0 ].groupby( 's' ).agg(s1 = ( 'i' , set ))
gr1 = df[df.t = = 1 ].groupby( 's' ).agg(s2 = ( 'i' , set ))
pd.concat([gr0, gr1], axis = 1 )
|
Использование справочников
Связывание таблиц подобно SQL (
df_ref - таблица справочник даёт свои значения в таблицу
df по ключу
i):
df_ref = pd.DataFrame({ 'i' :[ 0 , 1 , 3 , 4 ],
'v' :[ 10 , 20 , 30 , 40 ],
's' :[ 'a' , 'b' , 'c' , 'd' ]})
df = pd.DataFrame({ "i" :[ 0 , 0 , 1 , 3 , 2 ] })
df.merge(df_ref, on = 'i' , how = "left" )
|
То-же если имена колонок для коннекта различные
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()
rng = pd.date_range( '2022-11-13' , periods = 3 , freq = 'D' )
rng.to_pydatetime()
|
Если дата-время в csv сохранена Unix timestamp (seconds passed since 1970-1-1)
то их можно конвертировать:
df = pd.DataFrame({ "date" : [ 1659304800 ,
1659304904 ]})
df.date = pd.to_datetime(df.date, unit = 's' )
|
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' )
df[ 'hour' ] = df.time.dt.hour
df[ 'weekday' ] = df.time.dt.weekday
|