Майнор по Анализу Данных, Группа ИАД-2

25/01/2017 Спасательная операция: Pandas, Matplotlib, NumPy.

Так как на предыдущем майноре вам не успели рассказать про рandas, matplotlib, numpy, то придется вас спасать.

Дисклеймер
Мы очень ограничены во времени и естественно стать гуру pandas, numpy, matplotlib и других модулей за такой краткий срок у вас не получится. Этому может способствовать только постоянная практика: наши домашние задания, онлайн курсы по анализу данных в python, видео лекции, ваша курсовая работа (?). Я постараюсь изложить основную суть. Для всего, что я не успею - можно спрашивать меня, google или RTFM.

Начнем.

In [ ]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

plt.style.use('ggplot')
plt.rcParams['figure.figsize'] = (16,8)

NumPy

NumPy (short for Numerical Python) - это эффективная библиотека для работы с числовыми массивами и матрицами.

Нам с вами очень важно уметь работать с этими структурами быстро по крайней мере по двум причинам:

  • Данные любой природы в подавляющем числе случаем можно преставить в виде набора чисел (изображения, тексты, видео-клипы, простые табличные данные)
  • Этих данных очень много и надо бы уметь их быстро обрабатывать, анализировать и модифицировать ( желательно избегая вложенных циклов, большого набора условных операторов!)

Основа numpy - это array (массивы). В отличие от списков или кортежей, элементы массива должны быть одного типа. Такая жертва оправдывает скорость и низкие затраты на хранение информации, которую дает numpy. Массивы же, в свою очередь, используются во многих других библиотеках python для анализа данных.

Массивы

Создание массива

Массив можно создать из списка.

In [ ]:
arr = np.array([1,3,4,7,12])
arr
In [ ]:
arr = np.array([1,3,4,7,12.0])
arr
In [ ]:
arr = np.array([1,3,4,'7',12.0])
arr

Но наиболее распространены создания "с нуля"

In [ ]:
arr = np.arange(0, 12, 0.7) # расширение функции range()
arr
In [ ]:
arr = np.linspace(0, 12, 20) # равномерно распределенные 20 чисел от 0 до 12
arr
In [ ]:
# Массив из "1"
arr = np.ones(7)
print arr
arr = np.ones(7, dtype=int)
print arr
In [ ]:
# Массив из "0"
arr = np.zeros(7)
In [ ]:
# Массив из чего хотите
arr = np.full(7, np.exp(1), )
arr

Свойства массива

In [ ]:
A = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5], [4, 1, 1]])
In [ ]:
print A.shape
print A.size
print A.ndim

Форму массива (shape) можно менять, но так, чтобы это согласовывалось с его размером (size)

In [ ]:
A = A.reshape((6,2))
A
In [ ]:
A = A.flatten()
A

#!!!
# Это не тоже самое, что A = A.reshape((1, 12))
In [ ]:
A = A.reshape((3,-1)) 
A

# -1 как бы означает, "сделай первую размерность равную 3, 
# а все остальное запихни во вторую, если получится
In [ ]:
A.T # Транспонирование матрицы

Индексация

Довольно стандартная и интуитивно понятная

In [ ]:
arr = np.arange(0, 19, 3)
arr
In [ ]:
arr[3]
In [ ]:
arr[:3]
In [ ]:
arr[3:5]
In [ ]:
arr[::3] #?!
In [ ]:
arr[-2:] #?!

Задание
Догадайтесь, как вывести массив в обратном порядке?

In [ ]:
## Your code here
In [ ]:
print arr>10
print arr[arr>10]
In [ ]:
arr[[1,3,2]]

На многомерные массивы (матрицы) все распространяется точно также.

In [ ]:
A = np.random.randint(0, 20, (5,6))
A
In [ ]:
# Небольшое дополнение
print A[:, 2]
print A[2, :]
In [ ]:
A[A>5]

Задание Задайте случайную матрицу размера (5,7) c числами от 5 до 34, такую что на каждой строчке числа поледовательно возрастают на 1. Первая строчка начинается с 5 и заканчивается 10, вторая начинается с 11 и тп.

In [ ]:
## Your code here

Склейка массивов

In [ ]:
a = np.random.randint(0, 10, (2, 5))
b = np.random.randint(0, 10, (2, 5))

print a
print b
In [ ]:
A = np.r_[a,b]
A
In [ ]:
A = np.concatenate((a,b), axis=0)
A
In [ ]:
A = np.c_[a,b]
A
In [ ]:
A = np.concatenate((a,b), axis=1)
A

Операции и функции на массивах

Тут все тоже довольно просто

In [ ]:
arr = np.arange(1,6, dtype=float)
arr
In [ ]:
1/arr
In [ ]:
arr * 2
In [ ]:
arr // 2
In [ ]:
bar = np.arange(6,1,-1)
bar
In [ ]:
arr + bar
In [ ]:
arr * bar
In [ ]:
arr ** bar
In [ ]:
# Матричное умножение (скалярное произведение)
arr.dot(bar)

В numpy реализовано много математических функций

In [ ]:
np.log(arr)
In [ ]:
np.sqrt(arr)

Задание
Задайте два случайных массива $a$ и $b$ одинаковой длины.

Вычислите следующие расстояния между массивами:

  • Euclidean Distance $$ d(a, b) = \sqrt{\sum_i (a_i - b_i)^2} $$
  • Manhattan Distance $$ d(a, b) = \sum_i |a_i - b_i| $$
  • Cosine Distance $$ d(a, b) = 1 - \frac{a^\top b}{||a||_2\cdot||b||_2}$$
In [ ]:
## Your code here

Задание
Выполните загрузку данных, как указано ниже (может занять время).

Выберите случайную строчку из данных. Найдите другую "ближайшую" (по евклидовому расстоянию) к ней строчку и выведите её.

In [ ]:
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces()
In [ ]:
X = faces.data
In [ ]:
## Your code here

Аггрегация

Массивы можно аггрегировать - считать среднее значение, медиану, моду, максимум, минимум, сумму и тп

In [ ]:
arr = np.random.rand(11)
arr
In [ ]:
print np.mean(arr)
print arr.mean()
In [ ]:
arr.sum()
In [ ]:
print 'максимальное значение %.4f находится на %d позиции' % (arr.max(), arr.argmax())
# аналогично argmax, есть argmin и argsort
In [ ]:
np.median(arr)
In [ ]:
np.percentile(arr, [15, 85])

Задание
Сгенерируйте такой случайный вектор (np.random.rand()) длины 10, что сумма его элементов равна 2.

In [ ]:
## Your code here

Задание
Сгенерируйте случайный вектор (np.random.rand()) длины 100. Выполните такое преобразование массива, что

  • Максимальному элементу(-ам) соответствовало число 1
  • Минимальному элементу(-ам) соответствовало число 0
  • Остальные элементы находились на интервале 0-1 с сохранением порядка
In [ ]:
## Your code here

Задание
Сгенерируйте случайный вектор длины 20 из целых чисел на интервале [0,50]. Оставьте в нем только те элементы что меньше 5 персентиля и больше 95 персентиля

In [ ]:
## Your code here

Что касается матриц - то в них все примерно тоже самое.

In [ ]:
A = np.random.rand(3,5)
A
In [ ]:
A.mean()

Задание
Сгенерируйте случайную матрицу размера $5 \times 6$ из целых чисел на интервале [0,50]. Выведите столбец с содержащий максимальное значение во всей матрице.

In [ ]:
## Your code here

Пропущенные значения

В numpy есть специальные обозначения для бесконечности и пропущенных значений.

В реальном мире приходится работать с очень "грязными" данными и частенько бывает, что какие-то измерения, значения признаков и тп просто отсутствуют. К этому надо быть готовым

In [ ]:
np.log(0)
In [ ]:
np.log(-1)
In [ ]:
np.nan
In [ ]:
arr = np.random.rand(10)
idx = np.random.randint(0, 10, 4)
arr[idx] = np.nan
arr
In [ ]:
# проверяем, является ли значение пропущенным
is_nan = np.isnan(arr)
In [ ]:
# проверяем, есть ли хотя бы одно пропущенное
np.any(is_nan)
In [ ]:
# проверяем, есть ли хотя бы одно пропущенное
np.all(is_nan)

Аггрегация массивов с пропущенными значениями может выполняться без учета np.nan

In [ ]:
print np.nanmean(arr)
print np.mean(arr)

Задание
Замените все пропущенные значение средним

In [ ]:
## Your code here

Линейная регрессия (Пока бездумно)

Загрузите файл 1 и файл 2 в папку с тетрадкой. С помощью функции loadtxt в модуле numpy загрузите табличные данные одного из файлов. Присвойте y = D[:,0] а X = D[:, 1:].

Сейчас мы воспользуемся одной магической формулой и построим модель линейной регрессии. Откуда эта формула берется мы узнаем на следующих занятиях.

Модель линейной регрессии в матричном виде выглядит так: $\hat{y} = X\hat{\beta}$, где

$$ \hat{\beta} = (X^T X)^{-1} X^T y $$

Остатки модели рассчитываются как $$ \text{res} = y - \hat{y} $$

Итак, еще раз:

  1. Загрузите данные
  2. Оцените веса $\beta$ с помощью формулы
  3. Постройте график, на котором по оси Y: остатки, а по оси X: $\hat{y}$
In [ ]:
# load data
D = np.loadtxt('tutorial_dataset_1.csv', 
               skiprows=1, 
               delimiter=',')
In [ ]:
 

Pandas

Основные структуры

Модуль pandas существенно упрощает исследование табличных данных в python. Работа в нем во многом напоминает работу с таблицами в SQL с тем отличием, что в pandas тебе не хочется рвать волосы на голове это делать гораздо удобнее, и в нем заложены некоторые дополнительные инструменты по работе с данными.

Series

Основными структурами являются Series и DataFrame.
Series – это проиндексированный одномерный массив значений. Он похож на простой словарь типа dict, где имя элемента будет соответствовать индексу, а значение – значению записи.

Задать Series можно многими способами, например с помощью массива:

In [ ]:
ser = pd.Series(np.random.rand(5))
In [ ]:
ser

Колонка слева - это (строчный) индекс - некоторая нумерация записанных значений

In [ ]:
ser.index
In [ ]:
ser.values
In [ ]:
# Достучаться до одного значения можно так
ser[1]
In [ ]:
# Можно так - это обычная интервальная индексация в python.
ser[0:2]

Но про то, как улучше находить нужные вам значения - чуть позже.

Индексом может быть что угодно, например:

In [ ]:
ser = pd.Series(np.random.rand(5), index=['m', 'i', 'n', 'o', 'r'])
In [ ]:
ser
In [ ]:
ser['r']
In [ ]:
ser['n':'o']

Индексация

Индексация в pandas может временами может показаться запутанной

In [ ]:
ser = pd.Series(np.random.rand(5), index=[1,3,5,6,9])
ser
In [ ]:
ser[3]
In [ ]:
ser[3:5] #?!

Поэтому придумали разные операторы для индексирования, чтобы можно было явно указать, когда вы хотите использовать значения индекса, а когда позицию.

In [ ]:
ser.loc[3:5, ]
In [ ]:
ser.loc[:, ]
In [ ]:
idx = ser > 0.5
ser.loc[idx, ]
In [ ]:
ser.iloc[3:5, ]

Их же используйте для присваивания!!

In [ ]:
idx = ser > 0.5
ser.loc[idx, ] = 0.5
ser
In [ ]:
 

DataFrame

DataFrame — это проиндексированный многомерный массив значений, соответственно каждый столбец DataFrame, является структурой Series. Индексирование в DataFrame ровно тоже, что и в Series, с тем отличием, что добавляется второе измерение.

In [ ]:
df = pd.DataFrame(np.random.randn(10, 3),
                  index=range(10),
                  columns=['A', 'B', 'C'])
In [ ]:
df.head() # выводит первые 5 (по-умолчанию) строк таблицы
In [ ]:
print df.index
print df.columns
In [ ]:
df.loc[1:3, ['A', 'B']]
In [ ]:
df.iloc[1:3, 0:2]

DataFrame тоже можно транспонировать!

In [ ]:
df.T
In [ ]:
# Краткая описательная статистика
df.describe() 

# Кстати, это тоже DataFrame

Аггрегация в DataFrame (по-умолчанию) происходит по стоблцам

In [ ]:
df.mean()
In [ ]:
df.A.mean()

Перевод данных в нужный тип

In [ ]:
df.A = df.A.astype(int)
df.head()
In [ ]:
print 'Количество уникальных значений в столбце А = %d' % df.A.nunique()
In [ ]:
print 'Самые большие значения в стоблце B :'
print df.B.nlargest(2)

# Гораздо быстрее, чем df.B.sort(ascending=False).iloc[:2]

Важно следить за данными, которые у вас хранятся в DataFrame

In [ ]:
df.dtypes
In [ ]:
df.loc[0, 'A'] = 'lalaley'
df.head()
In [ ]:
df.A

Удаление\добавление строк\столбцов

In [ ]:
df.head()
In [ ]:
df.drop(0, axis=0)
# Пока df не изменился !
In [ ]:
df.drop('A', axis=1)
In [ ]:
df.loc[:, 'D'] = np.nan
df.head()
In [ ]:
df.loc[10, :] = 0
df.tail()

Продолжим обучение на "реальных данных"

В 1968 году была опубликована статья под интригующем названием Correlation of Performance Test Scores with Tissue Concentration of Lysergic Acid Diethylamide in Human Subjects.

К статье приложен небольшой набор данных, состоящий из 7 наблюдений

In [ ]:
df = pd.read_csv('drugs-and-math.csv', 
                 index_col=0, 
                 sep=',')
In [ ]:
df.head()
In [ ]:
print df.shape
print df.columns
print df.index

Таблица уже отсортирована по колонке Drugs - отсортируем по колонке Score

In [ ]:
df = df.sort_values('Score', 
                    ascending=False)
In [ ]:
df.head()
In [ ]:
df.describe().T # Иногда так лучше
In [ ]:
df.plot(kind='box')
In [ ]:
df.Drugs.hist()
In [ ]:
# df.plot(x='Drugs', y='Score')
df.plot(x='Drugs', y='Score', kind='scatter')

Мы явно видим тенденцию..

Качество вина

Загрузите датасет с информацией о характеристиках вина и его качестве.

In [ ]:
## Your code here
  • Что из себя представляет объект в этом наборе данных? Сколько их?
  • Какие признаки описывают объекты? Сколько их?
  • Какой признак является целевым?
  • Каковы их области значений?
  • Есть ли пропуски?
In [ ]:
## Your code here

Какие признаки больше всего влияют на целевую переменную?

In [ ]:
## Your code here

Создайте новый столбец quality_cat, которая будет иметь значение "good" если quality > 5 и "bad" - иначе.

In [ ]:
## Your code here

Нарисуйте гистрограммы признака alcohol в группах с quality_cat == "good" и quality_cat == "bad".

In [ ]:
## Your code here

Можете ли вы придумать правило для классификации вина на хорошее и плохое по рисунку выше? Пусть это будет нашей первой моделью)

Напишите функцию brute_clf_train() которая бы перебирала пороговое значение по признаку alcohol и находило бы "оптимальное" (кстати, что значит оптимальное?)

In [ ]:
## Your code here

Напишите функцию brute_clf_predict() которая бы по значению признака alcohol и найденному выше порогу говорила какое качество у вина.

А заодно выводила бы количество "ошибок" на текущем наборе данных

Проверим, как обобщается наша модель на другие данные.

  • Загрузите другой датасет
  • Выполните те же панипуляции с признаками
  • Используйте нашу простейшую модель для предсказания качества на новых данных
In [ ]:
## Your code here