Домашнее задание по лекции "Подготовка данных (Data preprocessing)", Масляков Глеб.
import numpy as np
import pandas as pd
from time import time
Задача: улучшить код со слайдов (если возможно).
перевести денежные суммы формата "string(sum$)" в целые числа "integer(sum)".
Генерация случайных данных.
$10$ миллионов записей от $0$ до $100000\$ $.
df = pd.DataFrame({'price($)': [str(i) + '$' for i in np.random.choice(a = range(int(1e5)), size = int(1e7))]})
df.shape
(10000000, 1)
df.head()
price($) | |
---|---|
0 | 1428$ |
1 | 32695$ |
2 | 19688$ |
3 | 78519$ |
4 | 80773$ |
Время работы примера из лекции.
%%time
df['.'] = df['price($)'].apply(lambda x: int(x[:-1]))
CPU times: user 7.51 s, sys: 470 ms, total: 7.98 s Wall time: 8.14 s
df.head()
price($) | . | |
---|---|---|
0 | 1428$ | 1428 |
1 | 32695$ | 32695 |
2 | 19688$ | 19688 |
3 | 78519$ | 78519 |
4 | 80773$ | 80773 |
Удаление получившихся результатов
del df['.']
Оптимизированная версия
%%time
df['.'] = df['price($)'].apply(lambda x: x.replace('$', '')).astype(int)
CPU times: user 5.3 s, sys: 228 ms, total: 5.52 s Wall time: 5.53 s
###########
Данный код ещё и лучше тем, что может применяться в ситуации, когда значок доллара стоит в начале строки.
###########
Проблема в строчке, где надо было перекодировать слова 'yes' и 'no' в числа $1$ и $0$. Её можно реализовать эффективнее.
Генерация данных
$30$ миллионов записей вида ['yes/no', 'warm/cool/cold']
f1 = lambda x: 'yes' if x == 1 else 'no'
def f2(x):
if x == 0:
return 'cool'
elif x == 1:
return 'cold'
else:
return 'warm'
df = pd.DataFrame({'ans': [f1(i) for i in np.random.choice(2, 30000000)],
'weather': [f2(i) for i in np.random.choice(3, 30000000)]})
df.head()
ans | weather | |
---|---|---|
0 | no | cool |
1 | yes | warm |
2 | yes | cool |
3 | no | warm |
4 | yes | warm |
Пример из лекции
%%time
dct = {'yes': 1, 'no': 0}
df['ans_coded'] = df['ans'].map(dct)
CPU times: user 2.03 s, sys: 264 ms, total: 2.29 s Wall time: 2.29 s
df.head()
ans | weather | ans_coded | |
---|---|---|---|
0 | no | cool | 0 |
1 | yes | warm | 1 |
2 | yes | cool | 1 |
3 | no | warm | 0 |
4 | yes | warm | 1 |
Удаляем результат
del df['ans_coded']
Оптимизированная версия. Используем встроенную функцию factorize.
%%time
df['ans_coded'] = df['ans'].factorize(sort=True)[0]
CPU times: user 1.3 s, sys: 350 ms, total: 1.65 s Wall time: 1.65 s
df.head()
ans | weather | ans_coded | |
---|---|---|---|
0 | no | cool | 0 |
1 | yes | warm | 1 |
2 | yes | cool | 1 |
3 | no | warm | 0 |
4 | yes | warm | 1 |
del df
На следующем слайде предлагается извлечь нижнее и верхнее давление из записи вида string(v.d./n.d.)
Генерация данных
$2$ миллиона записей. Верхнее от $0$ до $200$, нижнее от $0$ до $150$
pressure = [str(np.random.choice(200)) + '/' + str(np.random.choice(150)) for _ in range(2000000)]
df = pd.DataFrame(pressure, columns = ['давление'])
df.head()
давление | |
---|---|
0 | 112/5 |
1 | 84/20 |
2 | 128/63 |
3 | 164/127 |
4 | 117/53 |
Пример из лекции
%%time
tmp = df['давление'].str.split('/')
df['в.давл.'] = tmp.apply(lambda x: x[0])
df['н.давл.'] = tmp.apply(lambda x: x[1])
CPU times: user 3.29 s, sys: 321 ms, total: 3.61 s Wall time: 3.65 s
df.head()
давление | в.давл. | н.давл. | |
---|---|---|---|
0 | 112/5 | 112 | 5 |
1 | 84/20 | 84 | 20 |
2 | 128/63 | 128 | 63 |
3 | 164/127 | 164 | 127 |
4 | 117/53 | 117 | 53 |
Удаляем результаты
del df['в.давл.']
del df['н.давл.']
Оптимизированная версия. Не делаем два apply, а сразу скармливаем pd.DataFrame предварительно переведя в формат list
%%time
df[['в.давл.', 'н.давл.']] = pd.DataFrame(df['давление'].str.split('/', 1).tolist(), columns = ['Давление_в','Давление_н'])
CPU times: user 2.38 s, sys: 214 ms, total: 2.6 s Wall time: 2.65 s
del df
Необходимо заполнить Nanы средними значениями. В лекции предложены три варианта заполнения: средним по всей выборке; средним по обучающей выборке; пропуски в обучающей выборке — средним по обучающей выборке, пропуски в тестовой выборке — средним по тестовой выборке.
Претензии по коду есть именно по третьему варианту.
Генерация данных
$6,000,000$ записей. Значения площадей от $0$ до $200$. Доля трейна и теста — $50\%$ (как на слайде). Доля Nanов — $\frac{1}{3}$ (тоже как на слайде).
df = pd.DataFrame(np.random.choice(200, size = (6000000, 4)), columns = ['площадь', 'площадь 1', 'площадь 2', 'площадь 3'])
x = np.array(['train'] * 3000000 + ['test'] * 3000000)
np.random.shuffle(x)
df['data'] = x
ind = np.arange(6000000)
np.random.shuffle(ind)
df.loc[ind[:2000000], 'площадь'] = np.nan
df.head(10)
площадь | площадь 1 | площадь 2 | площадь 3 | data | |
---|---|---|---|---|---|
0 | 21.0 | 180 | 158 | 74 | train |
1 | 49.0 | 158 | 83 | 94 | test |
2 | NaN | 76 | 167 | 60 | train |
3 | 125.0 | 47 | 81 | 56 | train |
4 | NaN | 54 | 96 | 123 | test |
5 | 35.0 | 20 | 69 | 168 | test |
6 | 2.0 | 56 | 152 | 136 | train |
7 | NaN | 197 | 19 | 171 | train |
8 | NaN | 116 | 54 | 38 | train |
9 | 47.0 | 77 | 101 | 67 | test |
Пример из лекции. Очень объёмный код.
%%time
df.loc[df['data'] == 'train', 'площадь'] = df[df['data'] == 'train']['площадь'].fillna(df[df['data'] == 'train']['площадь'].mean())
df.loc[df['data'] == 'test', 'площадь'] = df[df['data'] == 'test']['площадь'].fillna(df[df['data'] == 'test']['площадь'].mean())
CPU times: user 3.91 s, sys: 265 ms, total: 4.18 s Wall time: 4.18 s
df.head(10)
площадь | площадь 1 | площадь 2 | площадь 3 | data | |
---|---|---|---|---|---|
0 | 21.000000 | 180 | 158 | 74 | train |
1 | 49.000000 | 158 | 83 | 94 | test |
2 | 99.454553 | 76 | 167 | 60 | train |
3 | 125.000000 | 47 | 81 | 56 | train |
4 | 99.512947 | 54 | 96 | 123 | test |
5 | 35.000000 | 20 | 69 | 168 | test |
6 | 2.000000 | 56 | 152 | 136 | train |
7 | 99.454553 | 197 | 19 | 171 | train |
8 | 99.454553 | 116 | 54 | 38 | train |
9 | 47.000000 | 77 | 101 | 67 | test |
Возвращаем Nanы на место
df.loc[ind[:2000000], 'площадь'] = np.nan
Напрашивается сделать группировку по столбцу "data". Также можно воспользоваться функцией transform.
%%time
df['площадь'] = df.groupby("data")['площадь'].transform(lambda x: x.fillna(x.mean()))
CPU times: user 1.71 s, sys: 281 ms, total: 1.99 s Wall time: 2.02 s
В одну строчку. В два раза быстрее.
df.head(10)
площадь | площадь 1 | площадь 2 | площадь 3 | data | |
---|---|---|---|---|---|
0 | 21.000000 | 180 | 158 | 74 | train |
1 | 49.000000 | 158 | 83 | 94 | test |
2 | 99.454553 | 76 | 167 | 60 | train |
3 | 125.000000 | 47 | 81 | 56 | train |
4 | 99.512947 | 54 | 96 | 123 | test |
5 | 35.000000 | 20 | 69 | 168 | test |
6 | 2.000000 | 56 | 152 | 136 | train |
7 | 99.454553 | 197 | 19 | 171 | train |
8 | 99.454553 | 116 | 54 | 38 | train |
9 | 47.000000 | 77 | 101 | 67 | test |
Можно попробовать ещё сильнее ускорить.
Возвращаем Nanы
df.loc[ind[:2000000], 'площадь'] = np.nan
Супер оптимизация
%%time
df.loc[df['площадь'].isnull(), 'площадь'] = df.groupby('data')['площадь'].transform('mean')
CPU times: user 510 ms, sys: 58.7 ms, total: 569 ms Wall time: 569 ms
df.head(10)
площадь | площадь 1 | площадь 2 | площадь 3 | data | |
---|---|---|---|---|---|
0 | 21.000000 | 180 | 158 | 74 | train |
1 | 49.000000 | 158 | 83 | 94 | test |
2 | 99.454553 | 76 | 167 | 60 | train |
3 | 125.000000 | 47 | 81 | 56 | train |
4 | 99.512947 | 54 | 96 | 123 | test |
5 | 35.000000 | 20 | 69 | 168 | test |
6 | 2.000000 | 56 | 152 | 136 | train |
7 | 99.454553 | 197 | 19 | 171 | train |
8 | 99.454553 | 116 | 54 | 38 | train |
9 | 47.000000 | 77 | 101 | 67 | test |
Ещё в несколько раз быстрее.