\n",
"\n",
"## Открытый курс по машинному обучению\n",
"Авторы материала: программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий и Data Scientist в Segmento Екатерина Демидова. Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#
Тема 1. Первичный анализ данных с Pandas
"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**[Pandas](http://pandas.pydata.org)** — это библиотека Python, предоставляющая широкие возможности для анализа данных. С ее помощью очень удобно загружать, обрабатывать и анализировать табличные данные с помощью SQL-подобных запросов. В связке с библиотеками `Matplotlib` и `Seaborn` появляется возможность удобного визуального анализа табличных данных."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Данные, с которыми работают дата саентисты и аналитики, обычно хранятся в виде табличек — например, в форматах `.csv`, `.tsv` или `.xlsx`. Для того, чтобы считать нужные данные из такого файла, отлично подходит библиотека Pandas.\n",
"\n",
"Основными структурами данных в Pandas являются классы `Series` и `DataFrame`. Первый из них представляет собой одномерный индексированный массив данных некоторого фиксированного типа. Второй - это двухмерная структура данных, представляющая собой таблицу, каждый столбец которой содержит данные одного типа. Можно представлять её как словарь объектов типа `Series`. Структура `DataFrame` отлично подходит для представления реальных данных: строки соответствуют признаковым описаниям отдельных объектов, а столбцы соответствуют признакам."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---------\n",
"\n",
"## Демонстрация основных методов Pandas \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Чтение из файла и первичный анализ"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Прочитаем данные и посмотрим на первые 5 строк с помощью метода `head`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv(\"../../data/telecom_churn.csv\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В Jupyter-ноутбуках датафреймы `Pandas` выводятся в виде вот таких красивых табличек, и `print(df.head())` выглядит хуже.\n",
"\n",
"Кстати, по умолчанию `Pandas` выводит всего 20 столбцов и 60 строк, поэтому если ваш датафрейм больше, воспользуйтесь функцией `set_option`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.set_option(\"display.max_columns\", 100)\n",
"pd.set_option(\"display.max_rows\", 100)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"А также укажем значение параметра `presicion` равным 2, чтобы отображать два знака после запятой (а не 6, как установлено по умолчанию."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.set_option(\"precision\", 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Посмотрим на размер данных, названия признаков и их типы**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(df.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Видим, что в таблице 3333 строки и 20 столбцов. Выведем названия столбцов:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(df.columns)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Чтобы посмотреть общую информацию по датафрейму и всем признакам, воспользуемся методом **`info`**:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(df.info())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`bool`, `int64`, `float64` и `object` — это типы признаков. Видим, что 1 признак — логический (`bool`), 3 признака имеют тип `object` и 16 признаков — числовые.\n",
"\n",
"**Изменить тип колонки** можно с помощью метода `astype`. Применим этот метод к признаку `Churn` и переведём его в `int64`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Churn\"] = df[\"Churn\"].astype(\"int64\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Метод **`describe`** показывает основные статистические характеристики данных по каждому числовому признаку (типы `int64` и `float64`): число непропущенных значений, среднее, стандартное отклонение, диапазон, медиану, 0.25 и 0.75 квартили."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.describe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Чтобы посмотреть статистику по нечисловым признакам, нужно явно указать интересующие нас типы в параметре `include`. Можно также задать `include`='all', чтоб вывести статистику по всем имеющимся признакам."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.describe(include=[\"object\", \"bool\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Для категориальных (тип `object`) и булевых (тип `bool`) признаков можно воспользоваться методом **`value_counts`**. Посмотрим на распределение нашей целевой переменной — `Churn`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Churn\"].value_counts()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"2850 пользователей из 3333 — лояльные, значение переменной `Churn` у них — `0`.\n",
"\n",
"Посмотрим на распределение пользователей по переменной `Area code`. Укажем значение параметра `normalize=True`, чтобы посмотреть не абсолютные частоты, а относительные."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Area code\"].value_counts(normalize=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Сортировка\n",
"\n",
"`DataFrame` можно отсортировать по значению какого-нибудь из признаков. В нашем случае, например, по `Total day charge` (`ascending=False` для сортировки по убыванию):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.sort_values(by=\"Total day charge\", ascending=False).head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Сортировать можно и по группе столбцов:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.sort_values(by=[\"Churn\", \"Total day charge\"], ascending=[True, False]).head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Индексация и извлечение данных"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`DataFrame` можно индексировать по-разному. В связи с этим рассмотрим различные способы индексации и извлечения нужных нам данных из датафрейма на примере простых вопросов.\n",
"\n",
"Для извлечения отдельного столбца можно использовать конструкцию вида `DataFrame['Name']`. Воспользуемся этим для ответа на вопрос: **какова доля нелояльных пользователей в нашем датафрейме?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Churn\"].mean()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"14,5% — довольно плохой показатель для компании, с таким процентом оттока можно и разориться."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Очень удобной является логическая индексация `DataFrame` по одному столбцу. Выглядит она следующим образом: `df[P(df['Name'])]`, где `P` - это некоторое логическое условие, проверяемое для каждого элемента столбца `Name`. Итогом такой индексации является `DataFrame`, состоящий только из строк, удовлетворяющих условию `P` по столбцу `Name`. \n",
"\n",
"Воспользуемся этим для ответа на вопрос: **каковы средние значения числовых признаков среди нелояльных пользователей?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[df[\"Churn\"] == 1].mean()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Скомбинировав предыдущие два вида индексации, ответим на вопрос: **сколько в среднем в течение дня разговаривают по телефону нелояльные пользователи**?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[df[\"Churn\"] == 1][\"Total day minutes\"].mean()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Какова максимальная длина международных звонков среди лояльных пользователей (`Churn == 0`), не пользующихся услугой международного роуминга (`'International plan' == 'No'`)?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[(df[\"Churn\"] == 0) & (df[\"International plan\"] == \"No\")][\"Total intl minutes\"].max()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Датафреймы можно индексировать как по названию столбца или строки, так и по порядковому номеру. Для индексации **по названию** используется метод **`loc`**, **по номеру** — **`iloc`**.\n",
"\n",
"В первом случае мы говорим _«передай нам значения для id строк от 0 до 5 и для столбцов от State до Area code»_, а во втором — _«передай нам значения первых пяти строк в первых трёх столбцах»_. \n",
"\n",
"В случае `iloc` срез работает как обычно, однако в случае `loc` учитываются и начало, и конец среза. Да, неудобно, да, вызывает путаницу."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.loc[0:5, \"State\":\"Area code\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.iloc[0:5, 0:3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Метод `ix` индексирует и по названию, и по номеру, но он вызывает путаницу, и поэтому был объявлен устаревшим (deprecated)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Если нам нужна первая или последняя строчка датафрейма, пользуемся конструкцией `df[:1]` или `df[-1:]`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[-1:]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Применение функций: `apply`, `map` и др."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Применение функции к каждому столбцу:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.apply(np.max)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Метод `apply` можно использовать и для того, чтобы применить функцию к каждой строке. Для этого нужно указать `axis=1`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Применение функции к каждой ячейке столбца**\n",
"\n",
"Допустим, по какой-то причине нас интересуют все люди из штатов, названия которых начинаются на 'W'. В данному случае это можно сделать по-разному, но наибольшую свободу дает связка `apply`-`lambda` – применение функции ко всем значениям в столбце."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[df[\"State\"].apply(lambda state: state[0] == \"W\")].head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Метод `map` можно использовать и для **замены значений в колонке**, передав ему в качестве аргумента словарь вида `{old_value: new_value}`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"d = {\"No\": False, \"Yes\": True}\n",
"df[\"International plan\"] = df[\"International plan\"].map(d)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Аналогичную операцию можно провернуть с помощью метода `replace`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df = df.replace({\"Voice mail plan\": d})\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Группировка данных\n",
"\n",
"В общем случае группировка данных в Pandas выглядит следующим образом:\n",
"\n",
"```\n",
"df.groupby(by=grouping_columns)[columns_to_show].function()\n",
"```\n",
"\n",
"1. К датафрейму применяется метод **`groupby`**, который разделяет данные по `grouping_columns` – признаку или набору признаков.\n",
"3. Индексируем по нужным нам столбцам (`columns_to_show`). \n",
"2. К полученным группам применяется функция или несколько функций."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Группирование данных в зависимости от значения признака `Churn` и вывод статистик по трём столбцам в каждой группе.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"columns_to_show = [\"Total day minutes\", \"Total eve minutes\", \"Total night minutes\"]\n",
"\n",
"df.groupby([\"Churn\"])[columns_to_show].describe(percentiles=[])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Сделаем то же самое, но немного по-другому, передав в `agg` список функций:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"columns_to_show = [\"Total day minutes\", \"Total eve minutes\", \"Total night minutes\"]\n",
"\n",
"df.groupby([\"Churn\"])[columns_to_show].agg([np.mean, np.std, np.min, np.max])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Сводные таблицы"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Допустим, мы хотим посмотреть, как наблюдения в нашей выборке распределены в контексте двух признаков — `Churn` и `Customer service calls`. Для этого мы можем построить **таблицу сопряженности**, воспользовавшись методом **`crosstab`**:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.crosstab(df[\"Churn\"], df[\"International plan\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.crosstab(df[\"Churn\"], df[\"Voice mail plan\"], normalize=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Мы видим, что большинство пользователей — лояльные и пользуются дополнительными услугами (международного роуминга / голосовой почты)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Продвинутые пользователи `Excel` наверняка вспомнят о такой фиче, как **сводные таблицы** (`pivot tables`). В `Pandas` за сводные таблицы отвечает метод **`pivot_table`**, который принимает в качестве параметров:\n",
"\n",
"* `values` – список переменных, по которым требуется рассчитать нужные статистики,\n",
"* `index` – список переменных, по которым нужно сгруппировать данные,\n",
"* `aggfunc` — то, что нам, собственно, нужно посчитать по группам — сумму, среднее, максимум, минимум или что-то ещё.\n",
"\n",
"Давайте посмотрим среднее число дневных, вечерних и ночных звонков для разных `Area code`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.pivot_table(\n",
" [\"Total day calls\", \"Total eve calls\", \"Total night calls\"],\n",
" [\"Area code\"],\n",
" aggfunc=\"mean\",\n",
").head(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Преобразование датафреймов\n",
"\n",
"Как и многие другие вещи, добавлять столбцы в `DataFrame` можно несколькими способами."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Например, мы хотим посчитать общее количество звонков для всех пользователей. Создадим объект `total_calls` типа `Series` и вставим его в датафрейм:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"total_calls = (\n",
" df[\"Total day calls\"]\n",
" + df[\"Total eve calls\"]\n",
" + df[\"Total night calls\"]\n",
" + df[\"Total intl calls\"]\n",
")\n",
"df.insert(loc=len(df.columns), column=\"Total calls\", value=total_calls)\n",
"# loc - номер столбца, после которого нужно вставить данный Series\n",
"# мы указали len(df.columns), чтобы вставить его в самом конце\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Добавить столбец из имеющихся можно и проще, не создавая промежуточных `Series`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Total charge\"] = (\n",
" df[\"Total day charge\"]\n",
" + df[\"Total eve charge\"]\n",
" + df[\"Total night charge\"]\n",
" + df[\"Total intl charge\"]\n",
")\n",
"\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Чтобы удалить столбцы или строки, воспользуйтесь методом `drop`, передавая в качестве аргумента нужные индексы и требуемое значение параметра `axis` (`1`, если удаляете столбцы, и ничего или `0`, если удаляете строки):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# избавляемся от созданных только что столбцов\n",
"df = df.drop([\"Total charge\", \"Total calls\"], axis=1)\n",
"\n",
"df.drop([1, 2]).head() # а вот так можно удалить строчки"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"--------\n",
"\n",
"\n",
"\n",
"## Первые попытки прогнозирования оттока\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Посмотрим, как отток связан с признаком *\"Подключение международного роуминга\"* (`International plan`). Сделаем это с помощью сводной таблички `crosstab`, а также путем иллюстрации с `Seaborn` (как именно строить такие картинки и анализировать с их помощью графики – материал следующей статьи.)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# надо дополнительно установить (команда в терминале)\n",
"# чтоб картинки рисовались в тетрадке\n",
"# !conda install seaborn\n",
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"plt.rcParams[\"figure.figsize\"] = (8, 6)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.crosstab(df[\"Churn\"], df[\"International plan\"], margins=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sns.countplot(x=\"International plan\", hue=\"Churn\", data=df)\n",
"plt.savefig(\"int_plan_and_churn.png\", dpi=300);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Видим, что когда роуминг подключен, доля оттока намного выше – интересное наблюдение! Возможно, большие и плохо контролируемые траты в роуминге очень конфликтогенны и приводят к недовольству клиентов телеком-оператора и, соответственно, к их оттоку. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Далее посмотрим на еще один важный признак – *\"Число обращений в сервисный центр\"* (`Customer service calls`). Также построим сводную таблицу и картинку."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.crosstab(df[\"Churn\"], df[\"Customer service calls\"], margins=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sns.countplot(x=\"Customer service calls\", hue=\"Churn\", data=df)\n",
"plt.savefig(\"serv_calls__and_churn.png\", dpi=300);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Может быть, по сводной табличке это не так хорошо видно (или скучно ползать взглядом по строчкам с цифрами), а вот картинка красноречиво свидетельствует о том, что доля оттока сильно возрастает начиная с 4 звонков в сервисный центр. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Добавим теперь в наш DataFrame бинарный признак — результат сравнения `Customer service calls > 3`. И еще раз посмотрим, как он связан с оттоком. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"Many_service_calls\"] = (df[\"Customer service calls\"] > 3).astype(\"int\")\n",
"\n",
"pd.crosstab(df[\"Many_service_calls\"], df[\"Churn\"], margins=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sns.countplot(x=\"Many_service_calls\", hue=\"Churn\", data=df)\n",
"plt.savefig(\"many_serv_calls__and_churn.png\", dpi=300);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Объединим рассмотренные выше условия и построим сводную табличку для этого объединения и оттока."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.crosstab(df[\"Many_service_calls\"] & df[\"International plan\"], df[\"Churn\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Значит, прогнозируя отток клиента в случае, когда число звонков в сервисный центр больше 3 и подключен роуминг (и прогнозируя лояльность – в противном случае), можно ожидать около 85.8% правильных попаданий (ошибаемся всего 464 + 9 раз). Эти 85.8%, которые мы получили с помощью очень простых рассуждений – это неплохая отправная точка (*baseline*) для дальнейших моделей машинного обучения, которые мы будем строить. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В целом до появления машинного обучения процесс анализа данных выглядел примерно так. Прорезюмируем:\n",
" \n",
"- Доля лояльных клиентов в выборке – 85.5%. Самая наивная модель, ответ которой \"Клиент всегда лоялен\" на подобных данных будет угадывать примерно в 85.5% случаев. То есть доли правильных ответов (*accuracy*) последующих моделей должны быть как минимум не меньше, а лучше, значительно выше этой цифры;\n",
"- С помощью простого прогноза , который условно можно выразить такой формулой: \"International plan = True & Customer Service calls > 3 => Churn = 1, else Churn = 0\", можно ожидать долю угадываний 85.8%, что еще чуть выше 85.5%\n",
"- Эти два бейзлайна мы получили без всякого машинного обучения, и они служат отправной точной для наших последующих моделей. Если окажется, что мы громадными усилиями увеличиваем долю правильных ответов всего, скажем, на 0.5%, то возможно, мы что-то делаем не так, и достаточно ограничиться простой моделью из двух условий. \n",
"- Перед обучением сложных моделей рекомендуется немного покрутить данные и проверить простые предположения. Более того, в бизнес-приложениях машинного обучения чаще всего начинают именно с простых решений, а потом экспериментируют с их усложнением. "
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
},
"name": "seminar02_part2_pandas.ipynb"
},
"nbformat": 4,
"nbformat_minor": 1
}