#!/usr/bin/env python
# coding: utf-8
# # Майнор по Анализу Данных, Группа ИАД-2
# ## 25/01/2017 Спасательная операция: Pandas, Matplotlib, NumPy.
# Так как на предыдущем майноре вам не успели рассказать про `рandas`, `matplotlib`, `numpy`, то придется вас спасать.
#
# **Дисклеймер**
# Мы очень ограничены во времени и естественно стать гуру `pandas`, `numpy`, `matplotlib` и других модулей за такой краткий срок у вас не получится. Этому может способствовать только постоянная практика: наши домашние задания, онлайн курсы по анализу данных в `python`, [видео лекции](http://www.dataschool.io/easier-data-analysis-with-pandas/), ваша курсовая работа (?). Я постараюсь изложить основную суть. Для всего, что я не успею - можно спрашивать меня, google или [RTFM](http://pandas.pydata.org/).
#
# Начнем.
# In[ ]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
get_ipython().run_line_magic('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](https://www.dropbox.com/s/kg9px9v3xfysak9/tutorial_dataset.csv?dl=0) и [файл 2](https://www.dropbox.com/s/f87gm612o144emx/tutorial_dataset_2.csv?dl=0) в папку с тетрадкой. С помощью функции `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 году была [опубликована](http://www.ncbi.nlm.nih.gov/pubmed/5676802) статья под интригующем названием Correlation of Performance Test Scores with Tissue Concentration of Lysergic Acid Diethylamide in Human Subjects.
#
# К статье приложен небольшой набор [данных](https://www.dropbox.com/s/ui14yeeckbc6z7c/drugs-and-math.csv?dl=0), состоящий из 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')
# Мы явно видим тенденцию..
# ### Качество вина
# Загрузите [датасет](https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv) с информацией о характеристиках вина и его качестве.
# 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` и найденному выше порогу говорила какое качество у вина.
#
# А заодно выводила бы количество "ошибок" на текущем наборе данных
# Проверим, как обобщается наша модель на другие данные.
#
# * Загрузите другой [датасет](https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv)
# * Выполните те же панипуляции с признаками
# * Используйте нашу простейшую модель для предсказания качества на новых данных
# In[ ]:
## Your code here