Data Science на прикладі датасету вина
Привіт, я Андрій Салата, Principal Data Scientist / Data Architect в компанії Sigma Software. Працюю в IT понад 15 років, із них понад сім років займаюся Data Science і викладаю курс із цієї дисципліни в Sigma Software University. Раніше я вже писав оглядові статті про цю дисципліну, а тепер вирішив розібрати на прикладі, чим саме займається Data Scientist.
В цій статті я пропоную розібрати конкретний датасет — так називається набір даних, з яким працюють спеціалісти з Data Science. Ми опишемо цей датасет, проаналізуємо, зробимо візуалізації та побудуємо моделі. Щоб було цікавіше, я обрав доволі веселий датасет, який складається з даних про вина відомого португальського бренду. Давайте у нього зануримося.
Знайомимось із датасетом
Отже, ми отримали якесь завдання і починаємо знайомство з даними. Про що вони? Ці дані відображають різні характеристики цих вин: кислотність, наявність різних компонентів і хімічних сполук, щільність, вміст алкоголю тощо. Усього дванадцять характеристик, одна з яких — оцінка за
Отже, маємо об’єктивні хімічні та фізичні характеристики вина і маємо оцінку якості цих вин. Ідея полягає в тому, щоб на базі цього датасету побудувати модель, яка буде прогнозувати якість вина (і відповідну оцінку від сомельє) залежно від об’єктивних характеристик вина. Погодьтеся, було б цікаво, знаючи склад і пропорції вина, більш-менш точно прогнозувати, чи це буде якісне вино, яке високо оцінять фахівці, чи якийсь шмурдяк. Спробуймо!
Досліджуємо змінні
Перше, що ми маємо зробити — провести так званий Exploratory Data Analysis (EDA), тобто спробувати знайти в нашому датасеті певні закономірності та аномалії. Також цей аналіз передбачає перевірку гіпотез і припущень за допомогою інструментів статистики та графічних зображень. Ну а потім на базі сформульованої гіпотези ми спробуємо побудувати модель.
Перш ніж робити висновки з даних, важливо дослідити всі змінні (у цьому випадку — характеристики вина). Нам доступні лише фізико-хімічні (вхідні) та сенсорні (вихідні) змінні (наприклад, немає даних про типи винограду, бренд вина, ціну продажу вина тощо).
Ось наші змінні:
- fixed acidity;
- volatile acidity;
- citric acid;
- residual sugar;
- chlorides;
- free sulfur dioxide;
- total sulfur dioxide;
- density;
- pH;
- sulphates;
- alcohol.
І є output variable
, наша цільова характеристика — це якість вина від нуля до десяти за десятибальною шкалою.
Output variable (based on sensory data):
- quality (score between 0 and 10).
Зчитування даних і первинний EDA
Ознайомившись із даними, Data Scientist має поставити перед собою правильні питання. Перше, що ми хочемо зрозуміти — як виглядає цей датасет, які його характеристики розподілу і як виглядають показники в ньому. Далі — чи можемо ми знайти якісь пропуски та заповнити їх за певною логікою?
[ ] import numpy as np # linear algebra import pandas as pd # data processing import seaborn as sns import matplotlib.pyplot as plt import os #Setting Style for Plotting plt.style.use('fivethirtyeight')
Як бачимо, Python-бібліотеки, які використовуються безпосередньо для роботи з даними — це, в основному, Numpy і Pandas.
Також може використовуватися Seaborn — це бібліотека, яка дозволяє робити красиві візуалізації. Вона спирається на потужну бібліотеку візуалізації Matplotlib, яка власне й дозволяє візуалізації. Seaborn — це по суті надбудова над Matplotlib, яка полегшує роботу і розширює можливості.
Отже, спочатку зчитуємо наші дані (можуть бути локально, або на Google Drive, як у нас):
[ ] from google.colab import drive drive.mount('/content/drive', force_remount=True) local_dir = os.path.abspath(os.getcwd()) if local_dir != 'drive/My Drive/Colab Notebooks/data': os.chdir('drive/My Drive/Colab Notebooks/data') Mounted at /content/drive
Потім завантажуємо їх:
[ ] df = pd.read_csv('winequality-red.csv') print('Dataset dimentions' + str(df.shape)) df.head()
І, нарешті, дивимося на наш датасет. Розглянемо, наприклад, перші п’ять значень (п’ять різних вин). Ми бачимо, що в них відрізняються показники кислотності, кількість залишкового цукру, сульфатів, щільність, pH, вміст спирту і так далі. І в кінці наш цільовий показник — оцінка якості вина. Ось так і будуть виглядати наші дані.
Далі ми можемо подивитися на shape, тобто на кількість наших даних:
[ ] df.shape (1599, 12)
Ми бачимо, що у нас майже 1600 рядків (очевидно, кількість протестованих вин) і 12 характеристик, за якими вони вимірюються.
Ми також можемо застосувати команду info
і перевірити, чи є у нас порожні значення або пропуски:
[ ] df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 1599 entries, 0 to 1598 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 fixed acidity 1599 non-null float64 1 volatile acidity 1599 non-null float64 2 citric acid 1599 non-null float64 3 residual sugar 1599 non-null float64 4 chlorides 1599 non-null float64 5 free sulfur dioxide 1599 non-null float64 6 total sulfur dioxide 1599 non-null float64 7 density 1599 non-null float64 8 pH 1599 non-null float64 9 sulphates 1599 non-null float64 10 alcohol 1599 non-null float64 11 quality 1599 non-null int64 dtypes: float64(11), int64(1) memory usage: 150.0 KB
Бачимо, що у нас немає пропусків, нульові значення відсутні. Це чудово — значить, наш датасет вже підготовлений і немає потреби його вичищати чи заповнювати порожні місця.
Описова статистика
Тепер ми маємо порахувати якусь описову статистику. На щастя, це настільки популярна операція в Data Science і в аналізі даних, що можна просто взяти готовий метод describe
, який все зробить за вас. Все, що вам потрібно — ввести df
(тобто ваш Data Frame), крапка, describe
— і ви одразу отримуєте описові статистики, які вам дають розуміння про закон розподілу кожного показника.
[ ] df.describe()
Що ми можемо сказати, подивившись на результати? Показник count
(кількість записів) всюди однаковий — це підтверджує, що пропуски відсутні. Якби показник відрізнявся, це вказувало б, що десь є пропуски.
Далі у нас є середнє значення fixed acidity (сталої кислотності) — трохи більш як вісім. Саме по собі середнє значення ніяк не характеризує розподіл. Натомість якщо ми бачимо середнє значення і standard deviation (наступний рядок), то ми вже можемо побудувати закон розподілу.
Далі ми маємо мінімальне і максимальне значення, розподілені від 4 до 16. Середнє, нагадаю, трохи більше за 8. Робимо висновок, що наше середнє значення зсунуте в бік мінімального. Значить, наш закон розподілу, як мінімум, є трошки спотвореним.
Крім того, у нас є інформація про медіану. І ми бачимо, що медіана досить сильно відрізняється від середнього значення. Це ще одна ознака того, що це закон розподілу не є нормальним.
Також у нас є інформація про
Наприклад, зверніть увагу на вміст алкоголю (alcohol). Мінімальне значення в нас 8,4, максимальне — 14.9, середнє 10,4, тобто розподіл теж тяжіє до нижньої позначки. Значить більшість вин у нашому датасеті доволі легкі, з низьким вмістом спирту.
Як зрозуміти цільовий розподіл змінних і чому це так важливо
Тепер давайте викличемо будь-яку з характеристик, наприклад, нашу цільову:
[ ] df.quality.unique() array([5, 6, 7, 4, 8, 3])
Можемо подивитися, які взагалі у нас є значення оцінки якості. На шкалі від 1 до 10 у вин із нашого датасету є оцінки з 3 до 8. Тобто геть неякісних і супер’якісних вин у датасеті не представлено. Тому, якщо ми будуватимемо модель, і вона нам прогнозуватиме якісь значення типу одинички чи десятки, то це, скоріш за все, якась аномалія. Як бачимо, експерти таких значень не виставляють.
Також ми можемо подивитися частотний розподіл — які оцінки скільки разів зустрічаються в нашому датасеті:
[ ] df.quality.value_counts() 5 681 6 638 7 199 4 53 8 18 3 10 Name: quality, dtype: int64
Бачимо, що переважна більшість вин із нашого датасету отримала оцінки 5 і 6. Майже 200 вин отримали високу сімку і понад 50 вин — четвірку, а вісімку і трійку отримали лише кільканадцять вин. Така картина типова для більшості законів розподілу.
Ми можемо також відобразити це на частотній діаграмі:
df['quality'].hist()
Подібним чином можемо проаналізувати і проілюструвати розподіл за вмістом алкоголю:
[ ] sns.lineplot(df.alcohol.quantile(np.arange(0,1,0.1))) <Axes: ylabel='alcohol'>
Що крутіший графік, то менше в нашому датасеті вин із відповідною характеристикою. Таким чином, робимо висновок, що в датасеті багато вин міцністю близько 9,5, а переважна більшість вин мають рівень алкоголю від 9,5 до 11,5.
А от якщо ми проаналізуємо вміст лимонної кислоти, то побачимо доволі рівномірний розподіл:
sns.lineplot(df["citric acid"].quantile(np.arange(0,1,0.1))) <Axes: ylabel='citric acid'>
Отже, за кожною характеристикою вина ми можемо проаналізувати та візуалізувати форму розподілу.
Шукаємо кореляцію (взаємозалежність) між показниками
Щоб використовувати лінійну регресію для моделювання, необхідно видалити корельовані змінні, щоб покращити вашу модель. Можна знайти кореляції за допомогою функції pandas “.corr()”
і візуалізувати кореляційну матрицю за допомогою теплової карти в seaborn
.
Темні відтінки представляють позитивну кореляцію, а світлі відтінки представляють негативну кореляцію. Якщо встановити annot=True
, ви отримаєте значення, за якими об’єкти співвідносяться між собою в клітинках сітки.
fig, ax = plt.subplots(figsize=(15,7)) sns.heatmap(df.corr(),cmap='viridis', annot=True) <Axes: >
На перетинах ми бачитимем кореляцію між показниками. Наприклад, чим вищий рівень pH, тим нижчий рівень fixed acidity, що цілком логічно.
Ця візуалізація дозволяє нам зрозуміти, які показники найбільше впливають на якість вина — це volatile acidity (змінна кислотність), вміст алкоголю, а також сульфатів та лимонної кислоти. Знак коефіцієнта кореляції говорить про напрямок цієї залежності.
Змінна кислотність має коефіцієнт кореляції −0,39 — таким чином, чим нижча змінна кислотність, тим вища оцінка вина. Водночас рівень, алкоголю має додатний коефіцієнт, тобто вина з більшим вмістом алкоголю переважно отримують вищі оцінки.
Важливо, що ми говоримо про узагальнену лінійну залежність. Тобто цілком можливі випадки якісних вин з низьким рівнем алкоголю, проте в основній масі тенденція є такою, як описана вище. І пам’ятаємо, що коефіцієнт кореляції в жодному разі не говорить про причинно-наслідковість зв’язку, а лише про його міць і напрямок. Ми не знаємо точно, чи дійсно професійним сомельє подобаються міцніші вина, чи винороби роблять міцнішими ті вина, які подобаються сомельє.
Як ідентифікувати викиди та аномалії
Тепер розгляньмо датасет на предмет аномалій і викидів. Є дуже популярний графік, який називається Boxplot. Він теж відображає закон розподілу і дозволяє оцінити стандартні значення та аномалії, викиди. Іноді аномалії відкидають, іноді замінюють іншими значеннями — це все залежить від стратегії, яку застосовує Data Scientist.
l = df.columns.values number_of_columns=12 number_of_rows = int(len(l)-1/number_of_columns) plt.figure(figsize=(number_of_columns,5*number_of_rows)) for i in range(0,len(l)): plt.subplot(number_of_rows + 1, number_of_columns ,i+1) sns.set_style('whitegrid') sns.boxplot(df[l[i]],color='red',orient='v') plt.tight_layout()
На прикладі останнього стовпчика з показником Quality ми ще раз бачимо, що більшість вин із датасету потрапляють в «бокс» оцінок
Як знайти нерівності розподілу
Є також багато інших графіків, які дозволяють оцінити сам розподіл. Наприклад, можемо оцінити форму розподілу, його щільність.
plt.figure(figsize=(2*number_of_columns, 5*number_of_rows)) for i in range(0,len(l)): plt.subplot(number_of_rows + 1, number_of_columns, i+1) sns.distplot(df[l[i]],kde=True)
Як бачимо на графіках, форма розподілу за більшістю показників є неправильною — окрім хіба що рівня pH. Більшість розподілів мають явно виражений зсув у лівий бік, деякі також мають декілька вершин. Це вади розподілу, і досвідчені Data Scientists знають, як давати цьому раду, перетворивши це в нормальні закони розподілу. Нормальний закон розподілу дозволяє швидше і краще навчати моделі.
Далі ми можемо оцінити характеристики зміщення і гостроти розподілу. Коефіцієнт зміщення (асиметрії) показує, на скільки ваш закон розподілу відрізняється від нормального. Так само можемо оцінити Kurtosis (коефіцієнт ексцесу).
print("Skewness \n ",df.skew()) print("\n Kurtosis \n ", df.kurt()) Skewness fixed acidity 0.982751 volatile acidity 0.671593 citric acid 0.318337 residual sugar 4.540655 chlorides 5.680347 free sulfur dioxide 1.250567 total sulfur dioxide 1.515531 density 0.071288 pH 0.193683 sulphates 2.428672 alcohol 0.860829 quality 0.217802 dtype: float64 Kurtosis fixed acidity 1.132143 volatile acidity 1.225542 citric acid -0.788998 residual sugar 28.617595 chlorides 41.715787 free sulfur dioxide 2.023562 total sulfur dioxide 3.809824 density 0.934079 pH 0.806943 sulphates 11.720251 alcohol 0.200029 quality 0.296708 dtype: float64
Перевіряємо розподіли та кореляції усіх змінних
Також ми можемо подивитися на ці розподіли в контексті кореляції, тобто взяти показники попарно і подивитися, як вони пов’язані між собою. Якщо це «хмаринка» в центрі графіку, то чіткої залежності між двома показниками немає.
Якщо ж хмаринка на графіку розтягнута з одного кута в інший, то ми побачимо певний паттерн — дані витягнуті вздовж якоїсь уявної лінії. Це означає, що між даними є залежність, і що ця залежність може ефективно використовуватись в побудові моделей.
Спробуємо додати сюди ще й третій показник — наш цільовий, який демонструє якість вина.
sns.pairplot(df, diag_kind='kde', hue='quality') <seaborn.axisgrid.PairGrid at 0x7f794e158e80>
Тут ми бачимо, наприклад, що показники залишкового цукру (residual sugar) та лимонної кислоти (citric acid) доволі сильно пов’язані між собою. Також варто звертати увагу на колір точок на графіку. Якщо є чітке розділення на темні та світлі точки — саме ці показники варто використовувати в фінальній моделі.
Здійснивши такий описовий аналіз, ми вже можемо відібрати певні показники, які будуть утворювати ядро нашої моделі.
Моделювання
Безпосередньо для моделювання ми можемо використовувати інструмент sklearn
— це бібліотека для машинного навчання. І також використовуємо всі знання, які ми здобули на попередніх етапах.
from sklearn.model_selection import train_test_split new_data = df.copy() features = new_data.drop(['quality'], axis = 1) labels = new_data['quality']
Наш датасет ми ділимо на дві частини:
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size = 0.4, random_state = 42) X_test, X_val, y_test, y_val = train_test_split(X_test, y_test, test_size = 0.5, random_state = 42) print (len(labels), len(y_train), len(y_val), len(y_test))
Тепер спробуємо застосувати модель лінійної регресії. Як бачимо, використання і побудова тієї чи іншої моделі — це буквально декілька рядочків коду (якщо використовувати її з «коробки» — тобто без тюнінгу і додаткового налаштування).
# importing module from sklearn.linear_model import LinearRegression # creating an object of LinearRegression class model1 = LinearRegression() # fitting the training data model1.fit(X_train,y_train) y_pred1 = model1.predict(X_test)
Ще важливий розділ — правильний підбір метрик, за якими ми оцінюємо, добре чи погано навчається модель. У цьому випадку ми використовуємо метрику r2
, яка демонструє, наскільки модель корелює з ідеальною лінією, яку ми описуємо. Також це може бути mean_square_error
і root_mean_squared error
. Використовуючи ці метрики, ми будуємо модель лінійної регресії.
# importing r2_score module from sklearn.metrics import r2_score from sklearn.metrics import mean_squared_error # predicting the accuracy score score1=r2_score(y_test,y_pred1) print('R2 score is: ',score1) print('mean_sqrd_error(MAE) is: ',mean_squared_error(y_test,y_pred1)) print('root_mean_squared error(RMSE) is: ',np.sqrt(mean_squared_error(y_test,y_pred1))) Тепер рахуємо показники: R2 score is: 0.338322640280049 mean_sqrd_error(MAE) is: 0.39229584165974557 root_mean_squared error(RMSE) is: 0.6263352470201127
Бачимо, що наш R2 score дорівнює 0,35, що не дуже добре. Чим ближче наш показник до одиниці, тим краще наша модель. Наш mean_sqrd_error
дорівнює 0,39, що не набагато краще.
Отже, наша модель хоч і бідно, але вміє прогнозувати наш цільовий показник якості вина. Пошук причин неточності моделі і її покращення — це і є робота спеціаліста з Data Science.
Ще одна важлива річ, з якої починається аналіз того, як поводить себе модель, це feature importance
, тобто важливість тих показників, які ми використовуємо.
Для деяких моделей є можливість її побудувати, і це дуже круто.
feat_importances = pd.Series(model1.coef_, index=X_train.columns) feat_importances.nlargest(25).plot(kind='barh',figsize=(10,10))
Ми бачимо, що найсильніший показник, який використовує наша модель, це щільність — density. Далі йде кількість хлоридів, вільних кислот і так далі. Показники з від’ємними значеннями (зокрема кількість сульфатів) можна виключити з моделі.
Ось із таких етапів і складається робота Data Scientist. Якщо вас зацікавив набір даних, який ми розглядали — він доступний у репозиторії машинного навчання UCI, а також на Kaggle.
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів