Домашнее задание по лекции "Подготовка данных (Data preprocessing)", Масляков Глеб.

In [1]:
import numpy as np
import pandas as pd
from time import time

Задача: улучшить код со слайдов (если возможно).

Пример $1$: (слайд $11$)

перевести денежные суммы формата "string(sum\$)" в целые числа "integer(sum)".

Генерация случайных данных.

$10$ миллионов записей от $0$ до $100000\$ $.

In [2]:
df = pd.DataFrame({'price($)': [str(i) + '$'  for i in np.random.choice(a = range(int(1e5)), size = int(1e7))]})
In [3]:
df.shape
Out[3]:
(10000000, 1)
In [4]:
df.head()
Out[4]:
price($)
0 1428$
1 32695$
2 19688$
3 78519$
4 80773$

Время работы примера из лекции.

In [5]:
%%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
In [6]:
df.head()
Out[6]:
price($) .
0 1428$ 1428
1 32695$ 32695
2 19688$ 19688
3 78519$ 78519
4 80773$ 80773

Удаление получившихся результатов

In [7]:
del df['.']

Оптимизированная версия

In [8]:
%%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']

In [11]:
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'
In [12]:
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)]})
In [13]:
df.head()
Out[13]:
ans weather
0 no cool
1 yes warm
2 yes cool
3 no warm
4 yes warm

Пример из лекции

In [14]:
%%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
In [15]:
df.head()
Out[15]:
ans weather ans_coded
0 no cool 0
1 yes warm 1
2 yes cool 1
3 no warm 0
4 yes warm 1

Удаляем результат

In [16]:
del df['ans_coded']

Оптимизированная версия. Используем встроенную функцию factorize.

In [17]:
%%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
In [18]:
df.head()
Out[18]:
ans weather ans_coded
0 no cool 0
1 yes warm 1
2 yes cool 1
3 no warm 0
4 yes warm 1
In [19]:
del df

Корректировка значений (слайд $13$)

На следующем слайде предлагается извлечь нижнее и верхнее давление из записи вида string(v.d./n.d.)

Генерация данных

$2$ миллиона записей. Верхнее от $0$ до $200$, нижнее от $0$ до $150$

In [20]:
pressure = [str(np.random.choice(200)) + '/' + str(np.random.choice(150)) for _ in range(2000000)]
df = pd.DataFrame(pressure, columns = ['давление'])
In [21]:
df.head()
Out[21]:
давление
0 112/5
1 84/20
2 128/63
3 164/127
4 117/53

Пример из лекции

In [22]:
%%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
In [23]:
df.head()
Out[23]:
давление в.давл. н.давл.
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

Удаляем результаты

In [24]:
del df['в.давл.']
del df['н.давл.']

Оптимизированная версия. Не делаем два apply, а сразу скармливаем pd.DataFrame предварительно переведя в формат list

In [25]:
%%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
In [26]:
del df

Заполняем пропуски средними значениями (слайд 19)

Необходимо заполнить Nanы средними значениями. В лекции предложены три варианта заполнения: средним по всей выборке; средним по обучающей выборке; пропуски в обучающей выборке — средним по обучающей выборке, пропуски в тестовой выборке — средним по тестовой выборке.

Претензии по коду есть именно по третьему варианту.

Генерация данных

$6,000,000$ записей. Значения площадей от $0$ до $200$. Доля трейна и теста — $50\%$ (как на слайде). Доля Nanов — $\frac{1}{3}$ (тоже как на слайде).

In [29]:
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
In [30]:
df.head(10)
Out[30]:
площадь площадь 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

Пример из лекции. Очень объёмный код.

In [31]:
%%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
In [32]:
df.head(10)
Out[32]:
площадь площадь 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ы на место

In [33]:
df.loc[ind[:2000000], 'площадь'] = np.nan

Напрашивается сделать группировку по столбцу "data". Также можно воспользоваться функцией transform.

In [34]:
%%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

В одну строчку. В два раза быстрее.

In [35]:
df.head(10)
Out[35]:
площадь площадь 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ы

In [36]:
df.loc[ind[:2000000], 'площадь'] = np.nan

Супер оптимизация

In [37]:
%%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
In [38]:
df.head(10)
Out[38]:
площадь площадь 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

Ещё в несколько раз быстрее.