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

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt # some imports to set up plotting
import seaborn as sns # pip install seaborn

import warnings
warnings.filterwarnings('ignore')

# Graphics in retina format are more sharp and legible
%config InlineBackend.figure_format = 'retina'

Считываем данные

# Берем данные отсюда: https://github.com/Laggg/data--for--students
# сначала считываем 2 строчки, чтобы понять разделитель колонок (по умолчанию разделитель - ",")
# в данном случае - разделитель колонок - символ ";"
pd.read_csv('beauty.csv', nrows=2)
wage;exper;union;goodhlth;black;female;married;service;educ;looks
0 5.73;30;0;1;0;1;1;1;14;4
1 4.28;28;0;1;0;1;1;0;12;3
df = pd.read_csv('beauty.csv', sep=';')
df.head()
wage exper union goodhlth black female married service educ looks
0 5.73 30 0 1 0 1 1 1 14 4
1 4.28 28 0 1 0 1 1 0 12 3
2 7.96 35 0 1 0 1 0 0 10 4
3 11.57 38 0 1 0 0 1 1 16 3
4 11.42 27 0 1 0 0 1 0 16 3

Строим некоторые виды графиков

  1. Обычный график, построенный по точкам. На вход может подаваться как лист, так и массив, серия значений. Возможно использовать следующие параметры:
  • linewidth ширина линии, соединяющей соседние точки
  • color цвет линии
  • marker вид точкек, которые соединяются линиями
  • linestyle стиль линии (сплошная, прерывистая, точка-тире и т.д.)
  • label подпись на легенде

Приведем пример графика с некоторыми параметрами, которые можно изменять. Все комбинации параметров смотрите в официальной документации https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html

x = [1,2,3,1,3,2]
x = np.array([1,2,3,1,3,2])
x = pd.Series(np.array([1,2,3,1,3,2]))

plt.figure(figsize=(8,4)) # можно указать явно ширину и высоту графика (строчка не обязательна)
plt.plot(x, linewidth=2, color='green', marker='*', linestyle='dashed', label='line_1')
plt.legend() # показывать легенду
plt.grid(color='gray', linestyle='-', linewidth=1.5) # включить отрисовку сетки c определенными параметрами
plt.yticks([0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5]) # можно явно указать, как подписывать ось
plt.xticks()
plt.xlabel('ось абсцисс')
plt.ylabel('ось ординат');
  1. Другой пример - scatter (где точки не соединяются прямыми линиями), с указанием точек (x1,y1),(x2,y2),(x3,y3)…
x = np.linspace(0, 5, 50) # создаем массив из 100 чисел float от 0 до 5 с равномернов шагом
y = x*(x - 2)*(x - 4)

plt.figure(figsize=(8,4)) # можно указать явно ширину и высоту графика (строчка не обязательна)
plt.scatter(x, y, label='line_1')
plt.legend() # показывать легенду
plt.grid(linewidth=1) # включить отрисовку сетки c определенными параметрами
plt.yticks()
plt.xticks()
plt.xlabel('ось абсцисс')
plt.ylabel('ось ординат');
  1. Гистограмма - график, показывающий распределение какой-либо величины, встречающуюся в данном объеме значений. По другому, график показыват сколько раз встречается из выборки каждое значение в ней. Построим гистограммы с помощью разных библиотек.

Один из главных параметров bins - обратная величина к ширине столбцов на графике

df['wage'].hist(figsize=(6, 4), bins=100);
sns.distplot(df['wage'], bins=10);

Можно сразу построить несколько гистограмм, относящихся к разным столбцам данных:

features = ['wage', 'exper']
df[features].hist(figsize=(10, 4), bins=60);
  1. CountPlot - столбчатая диаграмма, чаще всего используется для категориальных признаков в данных. Показывает, сколько трочек в df имеют каждое из выбранного значения категориального признака.
sns.countplot(x='female', data=df);
sns.countplot(y='female', data=df);

Приведем пример для столбца look относительно параметра female в DataFrame df

sns.countplot(x='female', hue='looks', data=df);

На следующем примере покажем, как нарисовать несколько графиков на одной картинке: axes - части графика. axes[0] - левая часть, а axes[1] - правая. Аналогично можно создать сетку 2х2 для 4х графиков (но для 4х графиков нужно указывать уже 2 координаты, например, ax=axis[1][1]).

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 4))

sns.countplot(x='female', data=df, ax=axes[0]);
sns.countplot(x='looks', data=df, ax=axes[1]);
  1. Круговая диаграмма отлично показывает соотношение частей:
plt.pie(df.groupby('female')['wage'].count()); # круговая диаграмми (pie)
  1. Ящик с усами, или boxplot Box plot состоит из коробки (поэтому он и называется box plot), усиков и точек. Коробка показывает интерквартильный размах распределения, то есть соответственно 25% (Q1) и 75% (Q3) перцентили.

Черта внутри коробки обозначает медиану распределения.

Усы отображают весь разброс точек кроме выбросов, то есть минимальные и максимальные значения, которые попадают в промежуток (Q1 - 1.5IQR, Q3 + 1.5IQR), где IQR = Q3 - Q1 — интерквартильный размах.

Точками на графике обозначаются выбросы (outliers) — те значения, которые не вписываются в промежуток значений, заданный усами графика.

sns.boxplot(x='wage', data=df);
_, axes = plt.subplots(1, 2, sharey=True, figsize=(6, 4))
sns.boxplot(data=df['wage'], ax=axes[0]);
sns.violinplot(data=df['wage'], ax=axes[1]);

Для большего понимания посмотреть на картинку из Wikipedia:

  1. joint plot: Для того, чтобы подробнее посмотреть на взаимосвязь двух численных признаков, есть еще и joint plot — это гибрид scatter plot и histogram. Посмотрим на то, как связаны между собой wage и exper.
sns.jointplot(x='wage', y='exper', data=df, kind='scatter');
sns.jointplot('exper', 'wage', data=df, kind="kde", color="r");

После всего вышесказанного, нужно отметить, что можно делать некоторые операции над DataFrame, и уже к ним применять метод .plot(…).

То есть pd.series.</plot/hist/.../>(....) = plt.</plot/hist/.../>(pd.series, ....)

df.groupby('looks').wage.sum().plot(kind='bar', rot=75, color='green');
df[features].plot(kind='density', subplots=True, layout=(1, 2),
                  sharex=False, figsize=(10, 4));

3D графики

from mpl_toolkits.mplot3d import Axes3D
from sklearn import datasets
from sklearn.decomposition import PCA

# import some data to play with
iris = datasets.load_iris()
X = iris.data[:, :2]  # we only take the first two features.
y = iris.target
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
X_reduced = PCA(n_components=3).fit_transform(iris.data)


# To getter a better understanding of interaction of the dimensions
# plot the first three PCA dimensions
fig = plt.figure(1, figsize=(8, 6))
ax = Axes3D(fig, elev=-150, azim=110)
ax.scatter(X_reduced[:, 0], X_reduced[:, 1], X_reduced[:, 2], c=y,
           cmap=plt.cm.Set1, edgecolor='k', s=40)

ax.set_title("First three PCA directions")
ax.set_xlabel("1st eigenvector")
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("2nd eigenvector")
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("3rd eigenvector")
ax.w_zaxis.set_ticklabels([])
plt.show()

Создание анимации

Покажемсоздание анимации на примере построения разделябщей прямой между данными: https://hsto.org/webt/h7/vn/dt/h7vndtkzlinfkyoqzpcmjxecubu.gif из статьи про SVM https://habr.com/ru/company/ods/blog/484148/

import matplotlib.animation as animation
from matplotlib.animation import PillowWriter
import matplotlib.lines as mlines

from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split

def newline(p1, p2, color=None): # функция отрисовки линии
    #function kredits to: https://fooobar.com/questions/626491/how-to-draw-a-line-with-matplotlib
    ax = plt.gca()
    xmin, xmax = ax.get_xbound()

    if(p2[0] == p1[0]):
        xmin = xmax = p1[0]
        ymin, ymax = ax.get_ybound()
    else:
        ymax = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmax-p1[0])
        ymin = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmin-p1[0])

    l = mlines.Line2D([xmin,xmax], [ymin,ymax], color=color)
    ax.add_line(l)
    return l


def one_image(w, X, Y): # фунцкия отрисовки одного кадра
    axes = plt.gca()
    axes.set_xlim([-4,4])
    axes.set_ylim([-1.5,1.5])
    d1 = {-1:'green', 1:'red'}
    im = plt.scatter(X[:,0], X[:,1], c=[d1[y] for y in Y])
    im = newline([0,-w[2]/w[1]],[-w[2]/w[0],0], 'blue')
    return im


# блок подготовки данных
iris = load_iris()
X = iris.data
Y = iris.target
pca = PCA(n_components=2)
X = pca.fit_transform(X)
Y = (Y == 2).astype(int)*2-1 # [0,1,2] --> [False,False,True] --> [0,1,1] --> [0,0,2] --> [-1,1,1]
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.4, random_state=2020)

history_w = np.array([[ 1.40100620e-02,  3.82414425e-02,  9.29992169e-03],
       [ 9.34759319e-02,  1.38405275e-02, -2.07000784e-02],
       [ 1.77059209e-01,  2.08938874e-02, -5.07000782e-02],
       [ 2.57582976e-01,  1.13119698e-02, -8.07000777e-02],
       [ 2.57845336e-01,  3.30044268e-02, -1.10700077e-01],
       [ 2.48875308e-01,  4.34713607e-02, -1.40700076e-01],
       [ 2.10330535e-01,  2.29165461e-02, -1.70700074e-01],
       [ 2.88030722e-01,  1.60452308e-02, -2.00700073e-01],
       [ 3.65670311e-01,  6.38421381e-04, -2.30700071e-01],
       [ 4.22698556e-01,  4.13726013e-03, -2.00700068e-01],
       [ 3.91374597e-01, -2.71359690e-03, -2.30700066e-01],
       [ 4.56157722e-01, -9.23192424e-03, -2.00700064e-01],
       [ 4.56157718e-01, -9.23192415e-03, -2.00700062e-01],
       [ 4.56157713e-01, -9.23192405e-03, -2.00700060e-01],
       [ 5.15303640e-01, -1.46237611e-02, -1.70700058e-01],
       [ 5.15303635e-01, -1.46237609e-02, -1.70700056e-01],
       [ 5.15303630e-01, -1.46237608e-02, -1.70700055e-01],
       [ 5.15303625e-01, -1.46237607e-02, -1.70700053e-01],
       [ 5.15303619e-01, -1.46237605e-02, -1.70700051e-01],
       [ 5.51273647e-01, -3.28065062e-02, -1.40700049e-01],
       [ 5.51273642e-01, -3.28065059e-02, -1.40700048e-01],
       [ 5.51273636e-01, -3.28065055e-02, -1.40700047e-01],
       [ 5.66508857e-01,  5.17263051e-03, -1.70700045e-01],
       [ 5.66508852e-01,  5.17263046e-03, -1.70700044e-01],
       [ 5.66508846e-01,  5.17263040e-03, -1.70700042e-01],
       [ 5.66508840e-01,  5.17263035e-03, -1.70700040e-01],
       [ 5.48868641e-01,  1.97012529e-02, -2.00700038e-01],
       [ 5.48868636e-01,  1.97012527e-02, -2.00700036e-01],
       [ 5.48868630e-01,  1.97012525e-02, -2.00700034e-01],
       [ 5.54086386e-01,  2.73468786e-02, -2.30700032e-01],
       [ 5.96543457e-01,  1.00993879e-02, -2.00700030e-01],
       [ 5.96543451e-01,  1.00993878e-02, -2.00700028e-01],
       [ 5.96543445e-01,  1.00993877e-02, -2.00700026e-01],
       [ 6.38206423e-01,  3.96740775e-03, -1.70700024e-01],
       [ 6.38206417e-01,  3.96740771e-03, -1.70700022e-01],
       [ 6.11201179e-01, -5.88772655e-03, -2.00700021e-01],
       [ 6.52906839e-01, -1.43675546e-02, -1.70700019e-01],
       [ 6.52906832e-01, -1.43675545e-02, -1.70700017e-01],
       [ 6.52906826e-01, -1.43675543e-02, -1.70700015e-01],
       [ 6.42197374e-01,  7.79748602e-04, -2.00700014e-01],
       [ 6.03251852e-01,  1.06133678e-02, -2.30700012e-01],
       [ 6.03251846e-01,  1.06133677e-02, -2.30700009e-01],
       [ 6.05295634e-01,  3.17685316e-02, -2.60700007e-01],
       [ 6.05295628e-01,  3.17685313e-02, -2.60700004e-01],
       [ 6.05295622e-01,  3.17685310e-02, -2.60700002e-01],
       [ 5.85487103e-01,  4.23576206e-02, -2.90699999e-01],
       [ 5.85487097e-01,  4.23576202e-02, -2.90699996e-01],
       [ 5.57651268e-01,  2.83422349e-02, -3.20699993e-01],
       [ 5.53558401e-01,  3.77632078e-02, -3.50699990e-01],
       [ 5.12157603e-01,  5.03918360e-02, -3.80699987e-01]])


fig = plt.figure()
ims = [] # набиваем в этот список кадры с помощью цикла
for i in range(50):
    im = one_image(history_w[i], X_train, Y_train)
    ims.append([im])

ani = animation.ArtistAnimation(fig, ims, interval=20, blit=True, # используем волшебную команду
                                repeat_delay=500)
writer = PillowWriter(fps=20) # устанавливаем фпс

ani.save("my_demo.gif", writer='imagemagick') # сохраняем
MovieWriter imagemagick unavailable; trying to use <class 'matplotlib.animation.PillowWriter'> instead.

Домашнее задание:

  1. ДЗ по 2м последним лабам (15 - pandas, 16 - визуализация): Нужно взять данные отсюда: https://github.com/Laggg/data–for–students (flight_delays.csv)
  2. для каждой задачи получить ответ на вопрос через pandas и визуализировать его любым подходящим способом (у всех графиков должна быть легенда, подписаны оси):
    • доля всех задержек ко всем вылетам
    • найти зависимость количества задержек от длины пути, который предстоит пролететь самолету
    • топ 5 направлений, для которых чаще всего происходят задержки
    • в какие времена года чаще всего происходят задержки рейсов
    • найти топ 10 самых хороших перевозчиков, которые реже всего задерживают свои рейсы
    • найти топ 10 самых безответственных аэропортов, в которых чаще всего происходят задержки
    • найти необычную зависимость количества задержек от имеющихся данных
import pandas as pd
pd.read_csv('flight_delays.csv').head(10)
Month DayofMonth DayOfWeek DepTime UniqueCarrier Origin Dest Distance dep_delayed_15min
0 c-8 c-21 c-7 1934 AA ATL DFW 732 N
1 c-4 c-20 c-3 1548 US PIT MCO 834 N
2 c-9 c-2 c-5 1422 XE RDU CLE 416 N
3 c-11 c-25 c-6 1015 OO DEN MEM 872 N
4 c-10 c-7 c-6 1828 WN MDW OMA 423 Y
5 c-8 c-3 c-4 1918 NW MEM MCO 683 N
6 c-1 c-27 c-4 754 DL PBI LGA 1035 N
7 c-4 c-29 c-6 635 OH MSP CVG 596 N
8 c-7 c-28 c-5 735 AA ONT DFW 1189 N
9 c-6 c-20 c-2 2029 OO DEN PSC 853 N