h2oai / h2oai/datatable

Useful funcs from real cases

Open
#2,938 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
1.9k
Forks
164
Avg merge
7h 31m
Merged PRs (30d)
1

Description

Hi, guys!

First of all want to say that you do a great thing. And I want to help to make it more fully, useful and popular. I have a lot of experience in analytics with pandas and r data.table and have my own list of functions that are used in 99% of cases. May be it could helps you to find your bottle neck because it is uncomfortable to use both pandas and python datatable at the same time and it will be good to use only your product. So, this is the list:
`

# Working with DB

con = pyodbc.connect(database_url) # any connection possible
df = pd.read_sql("SELECT * FROM data_table", con=con) # read_sql is a convenience wrapper around read_sql_table & read_sql_query

# загрузка данных в БД
con.execute("DROP TABLE IF EXISTS data_table") # any sql is ok
weather_df.to_sql(name="data_table", con=con, if_exists='replace', index=False) # if_exists : {'fail', 'replace', 'append'}

# Исследование данных

df.index
df.columns # названия колонок
df.values # данные возвращаются в виде массива ndarray
df.shape # number of rows and columns in dataframe
df.dtypes

s.unique() # Series of unique values
s.nunique() # кол-во уникальных значений
s.value_counts(dropna=False) # абсолютное кол-во
s.value_counts(normalize=True) # относительное кол-во

# подсчет кол-ва уникальных наблюдний для numpy
sign_classes, class_indices, class_counts = np.unique(X, return_index=True, return_counts=True)

df.head() # df[:10]
df.describe() # статистика по числовым признакам
df.describe(include=['object', 'bool']) # статистика по нечисловым признакам
df.describe(include="all").T # удобное описание всех даннных
df.apply(pd.Series.value_counts) # прим указанную функцию ко всем колонкам
df.count() # считает кол-во non-null значений в каждой колонке
df.info() # Index, Datatype and Memory information

'state' in df['col1'] # проверка наличия данных

df.corr() # корреляция. np.corrcoef(df.values.T)
df.corrwith(array) # считает корреляцию с внешними данными # df.corrwith(df['Shrek']).reset_index()
df.sum(axis=1) # по умолчанию все пропуски игнорируются
df.cumsum() # кумулятивная сумма
df.idxmax() # индекс максимального элемента в массиве

import pandas_profiling
pandas_profiling.ProfileReport(df) # подробное описание данных

# Работа с пропусками

df.isna() # генерирует булеву маску для отсутствующих значений
df.isnull() # тоже самое что и isna()
df.notnull() # противоположность метода isnull()

df.nunique() # кол-во уникальных значений
df.isnull().sum() # кол-во пропусков по столбцам
df.isna().sum() # isna() тоже самое, что и isnull()

# удаление пропусков
df.dropna(axis=0) # удаление пропусков по строкам
df.dropna(axis=1) # удаление пропусков по столбцам
df.dropna(how='all') # how='any' - default
df.dropna(thresh=4) # thresh=np.int((100-percent_NA_cols_required)*(len(df.columns)/100)) --> это порог ОТЛИЧНЫХ от NA значений
df.dropna(subset=['col1']) # only drop rows where NaN appear in specific columns (here: 'col1')

# замена пропусков (inplace doesn`t work)
df = df.loc[df.isnull()] = 0 # замена при помощи маски
df = df.fillna(0, axis=0) # замена пропусков нулями
df = df.fillna({'col1': 0.5, 'col2': -1}) # значения замены для каждой колонки
df = df.fillna(method='ffill') # предыдущее значение в текущую ячейку
df = df.fillna(method='bfill') # следующее значение в текущую ячейку

# Выборки

# basic operations
df['col1'] # Series
df[['col1']] # DataFrame
df['col1'].unique() # выбор уникальных значений

# drop columns
del df['col1'] # df.drop(columns='col1'))
df.drop(['col1','col2'], axis=1) # удалить колонки
df.drop(df.columns[[1,3]], axis=1) # удалить по порядковому номеру
df.drop(df.columns[1:4], axis=1) # df[,-c(2:4)]

# drop rows
df.drop([1, 2], axis=0) # удалить строчки по имени
df.drop(df.index[[1,3]], axis=0) # удалить строчки по индексу
df.drop(index=[1,3], axis=0, inplace=True)

# selects
df[:10][['col2','col3']] # так делать не рекомендуется
df.loc[:9, ['col2','col3']] # лучше так (исп реальные индексы и имя столбца)
df.iloc[:10, 1:3] # или так (исп порядковый номер строки и порядковый номер столбца)

df.loc['NY':'LA', 'col1'] # если бы индексы были штатами
df.loc[['NY','LA'], 'col1'] # доступ к списку строк
df.loc[:, 'col1':'col3']

# conditions
df[df.col1 == 100] # выбрать все данные по условию
df.loc[df.col1 == 100, ['col1','col2']] # выбрать конкретные колонки по условию. Никода не пиши так: df[df.col1 == 100][['col1','col2']]
df[df.col1 == 100].iloc[:, [0,2]] # выбрать конкретные колонки с iloc. использование loc для условия mask уже не важно

# contains any 1 element
df[df.year != 2001] # аналогично для строк: df[df.city != 'NY']
df[~(df.year == 2001)] # аналогично для строк: df[~(df.city == 'NY')]

# contains 1+ element
df[df['state'].isin(['Florida','California'])] # аналогично для цифр: df[df['year'].isin([2015, 2020])]
df[df['state'].str.contains('loRid', flags=re.IGNORECASE)] # только для строк - ищет вхождения без учета регистра

# new column by condition
df['color'] = np.where(df.Type == 'A', 'green', 'red') # 2 results condition
df['color'] = np.select([(df.Type == 'A'), (df.Type == 'B')], ['yellow', 'blue'], default='purple') # 2+ results condition
df['color'] = df['Type'].apply(lambda x: 1 if x > 0 else 0) # только для одной колонки
df['color'] = df['Type'].map(lambda x: 'A' if ((x == 'A' and x == 'B') or x != 'C' else 0)) # только для одной колонки

# метод assign
df.assign(col_name = df.col1 + df.col2, col_name = df.col1 * df.col2)

# метод eval()
df['col4'] = df.eval('(col1 + col2) / (col3 - 1)') # df['col4'] = (df['col1'] + df['col2']) / (df['col3'] - 1)
df.eval('col4 = col1 + @local_var', inplace=True) # df['col4'] = df['col1'] + local_var

# метод query()
df.query('col1 == "NY" & col2 > 0') # df[(df.col1 == "NY") & (df.col2 > 0)]
df.query('col1 < @local_var & col2 > @local_var') # df[(df.col1 < local_var) & (df.col2 > local_var)]
df.query('col1 == @local_var') # if local_var = ['Florida','California']

# Замена значений

df['year'].where(df['year'] > 2001, 'yes', 'no', inplace=True) # замена с условием if-else
df['state'].where(df['state'].str.contains('Nevada', flags=re.IGNORECASE), 'yes', 'no')

df['state'].replace([-1, np.nan], 100) # список под замену
df['state'].replace({'Nevada': 'Nvd', np.nan: 'unknown'}) # df['state'].replace(['Nevada', np.nan], ['Nvd', 'unknown'])
df['state'].replace('Nevada', 'Nvd', inplace=True) # все значения 'Nevada' в колонке 'state' заменить на 'Nvd'
df['state'].str.replace('([A-Za-z]+)', 'New_value') # для поиска и замены значений так же можно исп регулярки

df.state[df.state == 'Nevada'] = 'Nvd' # тоже самое с обычным присвоением и можно прописать условие
df.year[df.year < 1900 | df.year > 2020] = 0

df['col1'].map({'A':1, 'B':2, 'C':3}) # вариант с функцией map

# Аrреrирование данных

df.groupby('col1').size() # SELECT col1, count(*) FROM df GROUP BY col1; - size() includes NaN values
df.groupby('col1').count() # SELECT col1, count(*) FROM df WHERE col1 IS NOT NULL GROUP BY col1; - count() does not

dict(list(df.groupby('col1'))) # col1 unique values will be keys of dict
dict(list(df.groupby([df.dtypes], axis=1))) # separates data Into different types
df.groupby(['col1','col2']).describe().unstack() # statistics by cols

# Any missing values in the group are excluded from the result

# aggregate
df.groupby('col1', as_index=False).agg(np.median) # не запихивать группировку в индекс
df.groupby('col1').agg([np.mean, np.std]) # df.groupby('col1').agg(['mean', 'std'])

df.groupby('col1').col2.nunique() # 1 col with agg use agg({'col1': pd.Series.nunique})
df.groupby('col1')['col2', 'col3'].sum() # 2 cols
df.groupby('col1')['col2', 'col3'].agg(['sum', 'max']) # 2 aggs for 2 cols

df.groupby('col1').agg({'col2':'sum', 'col3':'mean'})
df.groupby('col1').agg({'col2':'sum', 'col3':['mean','max']}).rename(columns={'sum':'sum_', 'mean':'mean_', 'max':'max_'})

df.groupby('col1')['col2'].quantile(0.75)
df.groupby('col1')['col2'].agg('describe')[['25%', '75%', 'count']]

# transform
# transform в отличаи от aggregate не изменяет структуру данных, а добавляет новую колонку с данными в существующую таблицу (как это делает к примеру sum() over(partition by))
df['new_col'] = df.groupby(['col1','col2']).col3.transform(np.sum) # df[, new_col:=sum(col3), by=.(col1, col2)]
df['new_col'] = df.groupby(['col1']).col3.transform(lambda x: x - x.mean())
df['sales_share'] = (df["sales"] / df.groupby(['location', 'store'])['sales'].transform(np.sum)) # dt[, pcnt_sales:=sales/sum(sales), by=c("location", "store")]

# apply
def func(x): return x**2
df['col1'].apply(func) # df['col1'].apply(lambda x: x**2)

df.groupby(['col1','col2']).col3.apply(lambda x: x - x.sum()).unstack() # прим функции к сгруппированным данным
df.groupby('col1').col2.apply(lambda x: ', '.join(x.values.astype(str))) # сгруппированные данные собрать в строку
df.groupby('col1').col2.apply(lambda x: x.values).reset_index() # сгруппированные данные собрать в список

# filter
df.filter(['col1','col2']).apply(lambda x: ' '.join(x.apply(lambda y: y).values.astype(str)), axis=1) # сгруппировать данные колонок ['col1','col2'] в одну

# функция map
df['new_col'] = df['gender'].map({'male': 1, 'female': 2})

# pivot_table
df.pivot_table(index='State', columns='Sex', values=['GDP', 'Pop'], aggfunc={'GDP':np.sum, 'Pop':'count'}, fill_value=0, margins=True, dropna=True) # margins=True добавляет total к pivot_table
df.pivot(index='Country', columns='State', values='Capital') # если агрегация не нужна, а нужно просто вставить non-numeric значения в поле values

# таблицы сопряженности (crosstab) - частный случай сводной таблицы
pd.crosstab(df.Class, df.Survived, margins=True, normalize=False) # normalize=True для вывода относительных значений, а не абсолютных. {0, 1} or {'all', 'index', 'columns'}

# melt (From Wide to Long)
pd.melt(team, id_vars=['Color'], # неизменяемый столбец. после преобразования каждое значение задублируется на кол-во колонок в value_vars
value_vars=['A', 'B', 'C'], # колонки, которые сгруппируются в одну с названием 'Team'
var_name='Team', value_name='Score') # значения, которые были в колонках value_vars сгруппируются в одну с названием 'Score'

# explode
df.explode('A') # если в значениях колонки 'A' хранится список и нужно задублировать каждую строку таблицы на кол-во элементов списка

# "groupby" for time series calls "resampling"

# группировка только по дате
df.resample('D', on='date').agg({'col1': len})
df.set_index('date').resample('D').agg({'col1': len})
df.groupby([pd.Grouper(key='date', freq='D')]).agg({'col1': len})

# группировка по неск колонкам, вкл дату
df.groupby('col2').resample('D', on='date').agg({'col1': len})
df.set_index('date').groupby('col2').resample('D').agg({'col1': len})
df.groupby(['col2', pd.Grouper(key='date', freq='D')])['price'].sum()

# OHLC - open, high, low , close
df['col1'].resample('AS').ohlc().bfill()
df.resample('AS').agg({'openbid': 'first', 'highbid': 'max', 'lowbid': 'min', 'closebid': 'last'})

# графика с разными типами усреднений по дате
df.col1.plot(label='Raw')
df.col1.rolling(window=28).mean().plot(label='28D MA') # оконная функция, берет диапозон [n; n-window]
df.col1.expanding().mean().plot(label='Expanding Average') # берет диапозон не минус n строк назад, а с конкретного места, например номера строки, по умолчанию = первой строки, т.е. берется накапительный диапозон данных
df.col1.ewm(alpha=0.03).mean().plot(label='EWMA($\\\\alpha=.03$)')
plt.legend()
plt.tight_layout()

# PARTITIONS

df.groupby('dep')['salary'].apply(lambda x: x.rank(method='first', ascending=False)) # ROW_NUMBER() OVER(PARTITION BY dep ORDER BY salary DESC)
df.groupby('dep')['salary'].apply(lambda x: x.rank(method='dense', ascending=False)) # RANK() OVER(PARTITION BY dep ORDER BY salary DESC)
# df.groupby('dep').salary.rank() # этот вариант почему-то стал работать через одно место

df.groupby('dep').salary.pct_change() # относительное изменение к предыдущему значению в группе. df.groupby('dep')['salary'].apply(lambda x: x.pct_change())
df.groupby('dep').salary.diff() # абсолютное изменение к предыдущему значению в группе
df.groupby('dep').date.diff().dt.days # изменения по датам. dt.days переводит формат даты в float64
df.groupby('dep').date.diff().astype('timedelta64[h]') # разница в часах
df.groupby('dep').salary.shift(1) # предыдущее значение в группе (LAG)
df.groupby('dep').salary.shift(-1) # следующее значение в группе (LEAD)

# удаление дубликатов

df.duplicated() # вернет булев массив для каждой строки, является ли эта строка дублем
df.drop_duplicates()
df[['col1']].drop_duplicates()
df[['col1', 'col2']].drop_duplicates() # удаляем дубли (по умолчанию дубли ищутся по всем строкам)

# объединение даннных

Inner join: pd.merge(df1, df2, left_on='lkey', right_on='rkey', how='inner', sort=False) # sort сортирует итоговую таблицу, но при больших объемах лучше его отключать
Inner join: pd.merge(df1, df2, left_on='lkey', right_on='rkey', how='inner', suffixes=('_left', '_right')) # merge(df1, df2, by.x="x_id", by.y="y_id")
Left join: pd.merge(df1, df2, left_on='lkey', right_on='rkey', how='left') # merge(df1, df2, by.x="x_id", by.y="y_id", all.x=TRUE, all.y=FALSE)
Outer join: pd.merge(df1, df2, left_on='lkey', right_on='rkey', how='outer') # merge(df1, df2, by.x="x_id", by.y="y_id", all=TRUE)
Cross join: df1['tmp'] = 1
df2['tmp'] = 1
pd.merge(df1, df2, on=['tmp']) # merge(df1, df2, by=NULL)

# простое объединение (Unions)
по вертикали:pd.concat([df1, df2], axis=1, ignore_index=True) # т.к. объединение роисходит по индексам, поэтому нужно что бы индексы былы одинаковы (если их нет - проблемы тоже нет)
Union all: pd.concat([df1, df2], axis=0) # total <- rbind(A, B)
Union: pd.concat([df1, df2]).drop_duplicates() # total <- unique(rbind(A, B))

# Работа с датами и временем

# создание дат и времени
date = pd.to_datetime("4th of July, 2015") # Timestamp
dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015', '2015-Jul-5', '06-07-2015', '20150707']) # DatetimeIndex

# изменение даты и времени
date.dt.date
date.dt.time
date.dt.year # date.dt.strftime('%Y')
date.dt.dayofyear # день года
date.dt.month # date.dt.strftime('%B')
date.dt.day # день месяца
date.dt.daysinmonth # == date.dt.days_in_month
date.dt.weekday # == date.dt.dayofweek == date.dt.strftime('%W')
date.dt.strftime('%A') # день недели - 'Saturday'
date.dt.strftime('%Y%m%d') # изменение формата

date + pd.to_timedelta(np.arange(12), 'D') # дата + диапазон 12 дней
date + pd.to_timedelta(14, 'D') # каждая дата списка + 14 дней
date + pd.DateOffset(months=3) # предыдущий способ не поддерживает месяца, поэтому можно исп этот способ для изменение дат по месяцам
dates - dates[0] # при мат операциях с датами возвращаются интервалы времени (в днях, т.к. мы сами задали эту периодичность). тип - TimedeltaIndex

# создание последовательностей
pd.date_range(start='2015-07-03', end='2015-07-10', periods=None, freq=offset, tz='Europe/London')
pd.date_range('2015-07-03', periods=8, freq='D') # последовательность дат в формате 'начальная дата-период'. freq - задает периодичность
pd.timedelta_range(0, periods=10, freq='2H30T') # последовательность по времени (2H30T - 2 hours 30 minutes)

# изменение последовательностей
dates.to_period('D')
df.asfreq('D', method=None) # asfreq работает c DataFrame с датами в индексах. если между датами есть пропуски, то можно их заполнить двумя методами: method='bfill' и method='ffill'

# сортировка значений

sorted(df.col, reverse=True) # реализация с numpy если 1 колонка
df.sort_values(['col2', 'col1']) # df[order(col2, col1)]
df.sort_values(['col2', 'col1'], ascending=[False, True]) # df[order(-col2, col1)]
df.sort_values('col2', ascending=False)[:1000] # сортируем по col2 и берем первые 1000 с конца
df.col1.nlargest(5) # top 5 largest vals. faster than sort_values(x, ascending=False).head(n)

# переименовка данных

df.reset_index() # индексы в колонку
df.set_index(df.col1) # колонку в индексы

df.columns = ['col1', 'col2', 'col3'] # one way
df.rename(columns={'col1':'A', 'col2':'B'}, inplace=True) # another way
df.rename(columns=str.upper, inplace=True) # columns to uppercase

# работа со строками

df['col1'].str.len() # длина объектов строки
df['col1'].str.lower()
df['col1'].str[0:3] # df.str.slice(0, 3)
df['col1'].str.extract('([A-Za-z]+)')
df['col1'].str.replace(str_or_regex, new)
df['col1'].str.contains('gmail|mail', flags=re.IGNORECASE) # проверка, содержит ли объект data слово gmail или mail
df['col1'].str.split()[0] # разбить строку и взять первый ее элемент
df['col1'].str.split().str.get(-1) # разбить строку и взять последний ее элемент

# изменение типа данных
(df['col1'] == 'Yes').astype(int) # 'object' ('Yes', 'No') --> (1, 0)
df['col1'].astype(float) # 'bool' (True, False) --> (1.0, 0.0)

# OHE with dummies

df = pd.DataFrame({'col1': ['Ь','Ь','а','с','а','Ь'], 'col2': ['B|C|D', 'B|D', 'A|C', 'B|D', 'B|C', 'B|C|D']})
dummies = pd.get_dummies(df['col1'], prefix='key', sparse=False)
dummies = df['col2'].str.get_dummies('|') # pd.get_dummies(df['col2'] also possible

# Работа с регулярками

import re

# поиск
re.findall(r'[A-Z0-9]{8,}$', text, flags=re.IGNORECASE) # 'findall' returns a list of matches
re.findall(r'(\w)\1', text, flags=re.IGNORECASE) # '\1' ищет буквы, которые повторяются дважды

# разбиение
re.split(r'[ab]', "carbs") # разбивка по a или b - ['c', 'r', 's']

# замена
re.sub(r'\s+', '_', text) # Как_защитить_металл_от_процесса_коррозии?
re.sub(r'(\w)\1', lambda r: r.group(0).upper(), text) # Как защитить метаЛЛ от процеССа коРРозИИ?
re.sub(r'\b(\w*(\w)\2\w*)\b', r'[\1]', text) # Как защитить [металл] от [процесса] [коррозии]? '\1' в данном случае ссылается на всё слово, которое мы нашли (\w*(\w)\2\w*)

# apply re
df['col'].apply(lambda x: re.sub(r'\s+', '_', x))
df['email'].str.findall('([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\\.([A-Z]{2,4})', flags=re.IGNORECASE)`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.