Визуализация на Python

Часть 1. Matplotlib

В начале как всегда настроим окружение: импортируем все необходимые библиотеки и немного настроим дефолтное отображение картинок.

! ls
07_manual.ipynb				    test.ipynb
beam_envelope_bend_test_with_elegant.ipynb  test.py
CarPrice_Assignment.csv			    titanic
developers_rend.html			    titanic.csv
iris_subset.txt				    Untitled1.ipynb
NumpyAndPandas.ipynb			    Untitled2.ipynb
old_warp				    Untitled.ipynb
out.npz					    video_games_sales.csv
ozon_student				    week_2_LinReg.ipynb
pairplot.png				    week_3_LR_trees.ipynb
practice_vis_py3.ipynb			    week_4_RF_boosting.ipynb
shared					    years_stats.html
# # для установки библиотек
# ! pip3 install seaborn
# ! pip3 install plotly
# ! pip3 install ggplot
# ! pip3 install matplotlib
# ! pip3 install matplotlib==3.0.0
# отключим предупреждения Anaconda
import warnings
warnings.simplefilter('ignore')

# будем отображать графики прямо в jupyter'e
%pylab inline
#графики в svg выглядят более четкими
%config InlineBackend.figure_format = 'svg' 

#увеличим дефолтный размер графиков
from pylab import rcParams
rcParams['figure.figsize'] = 10,5
import pandas as pd
import seaborn as sns
%pylab is deprecated, use %matplotlib inline and import the required libraries.
Populating the interactive namespace from numpy and matplotlib

После этого загрузим в dataframe данные, с которыми будем работать. Для примеров визуализаций я выбрала данные о продажах и оценках видео-игр с Kaggle Datasets. Данные об оценках игр есть не для всех строк, поэтому сразу оставим только те записи, по которым есть полные данные.

df = pd.read_csv('video_games_sales.csv')
print(df.shape)
(16719, 16)
df.head()
Name Platform Year_of_Release Genre Publisher NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales Critic_Score Critic_Count User_Score User_Count Developer Rating
0 Wii Sports Wii 2006.0 Sports Nintendo 41.36 28.96 3.77 8.45 82.53 76.0 51.0 8 322.0 Nintendo E
1 Super Mario Bros. NES 1985.0 Platform Nintendo 29.08 3.58 6.81 0.77 40.24 NaN NaN NaN NaN NaN NaN
2 Mario Kart Wii Wii 2008.0 Racing Nintendo 15.68 12.76 3.79 3.29 35.52 82.0 73.0 8.3 709.0 Nintendo E
3 Wii Sports Resort Wii 2009.0 Sports Nintendo 15.61 10.93 3.28 2.95 32.77 80.0 73.0 8 192.0 Nintendo E
4 Pokemon Red/Pokemon Blue GB 1996.0 Role-Playing Nintendo 11.27 8.89 10.22 1.00 31.37 NaN NaN NaN NaN NaN NaN
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 16719 entries, 0 to 16718
Data columns (total 16 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   Name             16717 non-null  object 
 1   Platform         16719 non-null  object 
 2   Year_of_Release  16450 non-null  float64
 3   Genre            16717 non-null  object 
 4   Publisher        16665 non-null  object 
 5   NA_Sales         16719 non-null  float64
 6   EU_Sales         16719 non-null  float64
 7   JP_Sales         16719 non-null  float64
 8   Other_Sales      16719 non-null  float64
 9   Global_Sales     16719 non-null  float64
 10  Critic_Score     8137 non-null   float64
 11  Critic_Count     8137 non-null   float64
 12  User_Score       10015 non-null  object 
 13  User_Count       7590 non-null   float64
 14  Developer        10096 non-null  object 
 15  Rating           9950 non-null   object 
dtypes: float64(9), object(7)
memory usage: 2.0+ MB
df = df.dropna()
print(df.shape)
(6825, 16)
df.head()
Name Platform Year_of_Release Genre Publisher NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales Critic_Score Critic_Count User_Score User_Count Developer Rating
0 Wii Sports Wii 2006.0 Sports Nintendo 41.36 28.96 3.77 8.45 82.53 76.0 51.0 8 322.0 Nintendo E
2 Mario Kart Wii Wii 2008.0 Racing Nintendo 15.68 12.76 3.79 3.29 35.52 82.0 73.0 8.3 709.0 Nintendo E
3 Wii Sports Resort Wii 2009.0 Sports Nintendo 15.61 10.93 3.28 2.95 32.77 80.0 73.0 8 192.0 Nintendo E
6 New Super Mario Bros. DS 2006.0 Platform Nintendo 11.28 9.14 6.50 2.88 29.80 89.0 65.0 8.5 431.0 Nintendo E
7 Wii Play Wii 2006.0 Misc Nintendo 13.96 9.18 2.93 2.84 28.92 58.0 41.0 6.6 129.0 Nintendo E
df['User_Score'] = df.User_Score.astype('float64')
df['Year_of_Release'] = df.Year_of_Release.astype('int64')
df['User_Count'] = df.User_Count.astype('int64')
df['Critic_Count'] = df.Critic_Count.astype('int64')
df.shape
(6825, 16)

Всего в таблице 6825 объектов и 16 признаков для них. Посмотрим на несколько первых записей c помощью метода head, чтобы убедиться, что все распарсилось правильно. Для удобства я оставила только те признаки, которые мы будем в дальнейшем использовать.

useful_cols = ['Name', 'Platform', 'Year_of_Release', 'Genre', 
               'Global_Sales', 'Critic_Score', 'Critic_Count',
               'User_Score', 'User_Count', 'Rating'
              ]
df[useful_cols].head(10)
Name Platform Year_of_Release Genre Global_Sales Critic_Score Critic_Count User_Score User_Count Rating
0 Wii Sports Wii 2006 Sports 82.53 76.0 51 8.0 322 E
2 Mario Kart Wii Wii 2008 Racing 35.52 82.0 73 8.3 709 E
3 Wii Sports Resort Wii 2009 Sports 32.77 80.0 73 8.0 192 E
6 New Super Mario Bros. DS 2006 Platform 29.80 89.0 65 8.5 431 E
7 Wii Play Wii 2006 Misc 28.92 58.0 41 6.6 129 E
8 New Super Mario Bros. Wii Wii 2009 Platform 28.32 87.0 80 8.4 594 E
11 Mario Kart DS DS 2005 Racing 23.21 91.0 64 8.6 464 E
13 Wii Fit Wii 2007 Sports 22.70 80.0 63 7.7 146 E
14 Kinect Adventures! X360 2010 Misc 21.81 61.0 45 6.3 106 E
15 Wii Fit Plus Wii 2009 Sports 21.79 80.0 33 7.4 52 E

Начнем с самого простого и зачастую удобного способа визуализировать данные из pandas dataframe — это воспользоваться функцией plot. Для примера построим график продаж видео игр в различных странах в зависимости от года. Для начала отфильтруем только нужные нам столбцы, затем посчитаем суммарные продажи по годам и у получившегося dataframe вызовем функцию plot без параметров.

В библиотеку pandas встроен wrapper для matplotlib.

[x for x in df.columns if 'Sales' in x]
['NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales', 'Global_Sales']
df1 = df[[x for x in df.columns if 'Sales' in x] + ['Year_of_Release']]\
    .groupby('Year_of_Release').sum()
df1.head()
NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales
Year_of_Release
1985 0.00 0.03 0.00 0.01 0.03
1988 0.00 0.02 0.00 0.01 0.03
1992 0.02 0.00 0.00 0.00 0.03
1994 0.39 0.26 0.53 0.08 1.27
1996 7.91 6.88 4.06 1.24 20.10
df1.plot();

svg

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

C помощью параметра kind можно изменить тип графика, например, на bar chart. Matplotlib позволяет очень гибко настраивать графики. На графике можно изменить почти все, что угодно, но потребуется порыться в документации и найти нужные параметры. Например, параметра rot отвечает за угол наклона подписей к оси x.

df1.plot(kind='bar', rot=45, stacked=True);

svg

Или можем сделать stacked bar chart, чтобы показать и динамику продаж и их разбиение по рынкам.

df1[list(filter(lambda x: x != 'Global_Sales', df1.columns))]\
    .plot(kind='bar', rot=45, stacked=True);

svg

df1[list(filter(lambda x: x != 'Global_Sales', df1.columns))]\
    .plot(kind='area', rot=45);

svg

Еще один часто встречающийся тип графиков - это гистограммы. Посмотрим на распределение оценок критиков.

df.Critic_Score.hist(bins = 20)
<Axes: >

svg

ax = df.Critic_Score.hist()
ax.set_title('Critic Score distribution')
ax.set_xlabel('critic score')
ax.set_ylabel('games')
Text(0, 0.5, 'games')

svg

У гистограмм можно контролировать, на сколько групп мы разбиваем распределение с помощью параметра bins.

ax = df.Critic_Score.hist(bins = 20)
ax.set_title('Critic Score distribution')
ax.set_xlabel('critic score')
ax.set_ylabel('games')
Text(0, 0.5, 'games')

svg

Еще немного познакомимся с тем, как в pandas можно стилизовать таблицы.

top_developers_df = df.groupby('Developer')[['Global_Sales']].sum()\
    .sort_values('Global_Sales', ascending=False).head(10)
    
top_developers_df
Global_Sales
Developer
Nintendo 529.90
EA Sports 145.93
EA Canada 131.46
Rockstar North 119.47
Capcom 114.52
Treyarch 101.37
Ubisoft Montreal 101.24
Ubisoft 94.53
EA Tiburon 79.77
Infinity Ward 77.56
# ! pip3 install jinja2
top_developers_df.style.bar()
  Global_Sales
Developer  
Nintendo 529.900000
EA Sports 145.930000
EA Canada 131.460000
Rockstar North 119.470000
Capcom 114.520000
Treyarch 101.370000
Ubisoft Montreal 101.240000
Ubisoft 94.530000
EA Tiburon 79.770000
Infinity Ward 77.560000
with open('developers_rend.html', 'w') as f:
    f.write(top_developers_df.style.bar().render())
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[94], line 2
      1 with open('developers_rend.html', 'w') as f:
----> 2     f.write(top_developers_df.style.bar().render())


AttributeError: 'Styler' object has no attribute 'render'

Полезные ссылки

Часть 2. Seaborn

Теперь давайте перейдем к библиотеке seaborn. Seaborn — это по сути более высокоуровневое API на базе библиотеки matplotlib. Seaborn содержит более адекватные дефолтные настройки оформления графиков. Если просто добавить в код import seaborn, то картинки станут гораздо симпатичнее. Также в библиотеке есть достаточно сложные типы визуализации, которые в matplotlib потребовали бы большого количество кода.

Познакомимся с первым таким "сложным" типом графиков pair plot (scatter plot matrix). Эта визуализация поможет нам посмотреть на одной картинке, как связаны между собой различные признаки.

import seaborn as sns
# c svg pairplot браузер начинает тормозить
%config InlineBackend.figure_format = 'png' 
sns_plot = sns.pairplot(
    df[['Global_Sales', 'Critic_Score', 'User_Score']]);
sns_plot.savefig('pairplot.png')

png

Также с помощью seaborn можно построить распределение, для примера посмотрим на распределение оценок критиков Critic_Score. Для этого построим distplot. По default'у на графике отображается гистограмма и kernel density estimation.

%config InlineBackend.figure_format = 'svg' 
sns.distplot(df.Critic_Score);

svg

Для того чтобы подробнее посмотреть на взаимосвязь двух численных признаков, есть еще и joint_plot – это гибрид scatter plot и histogram (отображаются также гистограммы распределений признаков). Посмотрим на то, как связаны между собой оценка критиков Critic_Score и оценка пользователя User_Score.

sns.jointplot(x='Critic_Score', y='User_Score', 
              data=df, kind='scatter');

svg

sns.jointplot(x='Critic_Score', y='User_Score', 
              data=df, kind='reg');

svg

Еще один полезный тип графиков - это box plot. Давайте сравним пользовательские оценки игр для топ-5 крупнейших игровых платформ.

top_platforms = df.Platform.value_counts().sort_values(ascending = False).head(5).index.values
sns.boxplot(x="Platform", y="Critic_Score", 
            data=df[df.Platform.isin(top_platforms)])
<Axes: xlabel='Platform', ylabel='Critic_Score'>

svg

Думаю, стоит обсудить немного подробнее, как же понимать box plot. Box plot состоит из коробки (поэтому он и называется box plot), усиков и точек. Коробка показывает интерквантильный размах распределения, то есть соответственно 25% (Q1) и 75% (Q3) процентили. Черта внутри коробки обозначает медиану распределения. С коробкой разобрались, перейдем к усам. Усы отображают весь разброс точек кроме выбросов, то есть минимальные и максимальные значения, которые попадают в промежуток (Q1 - 1.5*IQR, Q3 + 1.5*IQR), где IQR = Q3 - Q1 - интерквантильный размах. Точками на графике обозначаются выбросы (outliers) - те значения, которые не вписываются в промежуток значений, заданный усами графика.

И еще один тип графиков (последний из тех, которые мы рассмотрим в этой части) - это heat map. Heat map позволяет посмотреть на распределение какого-то численного признака по двум категориальным. Визуализируем суммарные продажи игр по жанрам и игровым платформам.

platform_genre_sales = df.pivot_table(
                        index='Platform', 
                        columns='Genre', 
                        values='Global_Sales', 
                        aggfunc=sum).fillna(0).applymap(float)

platform_genre_sales
Genre Action Adventure Fighting Misc Platform Puzzle Racing Role-Playing Shooter Simulation Sports Strategy
Platform
3DS 30.81 2.00 2.63 4.48 27.61 2.63 13.89 18.94 1.02 16.08 2.20 0.94
DC 0.00 1.33 0.56 0.00 0.12 0.00 0.20 0.68 0.05 0.52 1.09 0.00
DS 42.43 8.83 3.37 68.82 55.02 50.50 29.93 60.31 6.40 42.71 6.01 8.00
GBA 23.21 4.54 3.28 8.59 40.36 5.47 12.60 21.00 1.40 2.03 5.93 3.34
GC 29.99 4.56 15.81 12.72 24.67 3.31 11.09 12.48 13.04 8.39 19.91 3.45
PC 25.45 1.42 0.13 3.02 0.46 0.19 3.18 44.68 36.34 40.34 6.54 25.37
PS 54.93 1.10 18.91 5.66 18.92 0.26 34.17 44.07 5.86 1.67 20.75 0.25
PS2 238.73 10.74 64.72 38.70 52.34 3.97 127.17 77.30 98.20 34.01 191.88 8.21
PS3 262.38 16.18 47.83 26.59 20.91 0.40 62.17 64.00 174.54 7.91 98.20 3.19
PS4 76.92 3.09 6.86 2.70 6.16 0.03 9.08 18.18 63.67 0.72 55.16 0.46
PSP 43.92 2.81 12.36 5.09 10.84 2.04 27.88 31.11 18.52 4.61 25.34 3.40
PSV 9.53 1.28 1.91 1.81 2.49 0.12 1.00 7.02 3.88 0.00 1.84 0.00
Wii 75.75 7.72 21.89 149.42 78.25 8.22 48.35 11.01 19.20 23.88 213.53 1.76
WiiU 13.61 0.08 1.22 10.93 21.33 1.30 7.09 1.26 5.56 0.20 2.39 1.11
X360 209.90 11.52 35.30 70.09 10.26 0.36 56.14 68.62 260.35 13.02 109.74 8.00
XB 36.53 1.98 10.92 3.56 7.44 0.10 23.44 12.50 60.33 6.60 46.75 1.92
XOne 29.07 1.57 2.25 4.08 0.62 0.00 8.84 8.21 48.12 0.01 26.59 0.21
sns.heatmap(platform_genre_sales, annot=False, fmt=".1f", center = True);

svg

Полезные ссылки

Часть 3. Plotly

Мы рассмотрели визуализации на базе библиотеки matplotlib. Однако, это не единственная опция для построения графиков на языке python. Познакомимся также с библиотекой plotly. Plotly - это open-source библиотека, которая позволяет строить интерактивные графики в jupyter.notebook'e без необходимости зарываться в javascript код.

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

Перед началом работы импортируем все необходимые модули и инициализируем plotly с помощью команды init_notebook_mode.

from plotly.offline import init_notebook_mode, iplot
import plotly.graph_objects as go
import plotly
import plotly.graph_objs as go

init_notebook_mode(connected=True)

Для начала построим line plot с динамикой числа вышедших игр и их продаж по годам.

global_sales_years_df = df.groupby('Year_of_Release')[['Global_Sales']].sum()
global_sales_years_df.head()
Global_Sales
Year_of_Release
1985 0.03
1988 0.03
1992 0.03
1994 1.27
1996 20.10
released_years_df = df.groupby('Year_of_Release')[['Name']].count()
released_years_df.head()
Name
Year_of_Release
1985 1
1988 1
1992 1
1994 1
1996 7
years_df = global_sales_years_df.join(released_years_df)
years_df.head()
Global_Sales Name
Year_of_Release
1985 0.03 1
1988 0.03 1
1992 0.03 1
1994 1.27 1
1996 20.10 7
years_df.columns = ['Global_Sales', 'Number_of_Games']
years_df.head()
Global_Sales Number_of_Games
Year_of_Release
1985 0.03 1
1988 0.03 1
1992 0.03 1
1994 1.27 1
1996 20.10 7

В plotly строится визуализация объекта Figure, который состоит из данных (массив линий, которые в библиотеке называются traces) и оформления/стиля, за который отвечает объект layout. В простых случаях можно вызывать функцию iplot и просто от массива traces.

trace0 = go.Scatter(
    x=years_df.index,
    y=years_df.Global_Sales,
    name='Global Sales'
)

trace1 = go.Scatter(
    x=years_df.index,
    y=years_df.Number_of_Games,
    name='Number of games released'
)

data = [trace0, trace1]
layout = {'title': 'Statistics of video games'}

fig = go.Figure(data=data, layout=layout)
#fig.show()
iplot(fig, show_link = False)