Графики для визуализации данных (Exploratory Graphs)

Важно отметить, что пирожковый график и 2мерная гистограмма (тепловая карта) доступны только начиная с matplotlib 1.3.1. Поэтому, возможно, вам нужно сделать $pip install matplotlib==1.3.1

Начнём с инициализации нужных модулей.

In [1]:
import matplotlib.pyplot as plt
from pandas import date_range,Series,DataFrame,read_csv, qcut
from pandas.tools.plotting import radviz,scatter_matrix,bootstrap_plot,parallel_coordinates
from numpy.random import randn
from pylab import *
import brewer2mpl
from matplotlib import rcParams

#colorbrewer2 Dark2 qualitative color table
dark2_colors = brewer2mpl.get_map('Dark2', 'Qualitative', 7).mpl_colors

rcParams['figure.figsize'] = (10, 6)
rcParams['figure.dpi'] = 150
rcParams['axes.color_cycle'] = dark2_colors
rcParams['lines.linewidth'] = 2
rcParams['axes.facecolor'] = 'white'
rcParams['font.size'] = 14
rcParams['patch.edgecolor'] = 'white'
rcParams['patch.facecolor'] = dark2_colors[0]
rcParams['font.family'] = 'StixGeneral'

def remove_border(axes=None, top=False, right=False, left=True, bottom=True):
    """
    Minimize chartjunk by stripping out unnecesasry plot borders and axis ticks
    
    The top/right/left/bottom keywords toggle whether the corresponding plot border is drawn
    """
    ax = axes or plt.gca()
    ax.spines['top'].set_visible(top)
    ax.spines['right'].set_visible(right)
    ax.spines['left'].set_visible(left)
    ax.spines['bottom'].set_visible(bottom)
    
    #turn off all ticks
    ax.yaxis.set_ticks_position('none')
    ax.xaxis.set_ticks_position('none')
    
    #now re-enable visibles
    if top:
        ax.xaxis.tick_top()
    if bottom:
        ax.xaxis.tick_bottom()
    if left:
        ax.yaxis.tick_left()
    if right:
        ax.yaxis.tick_right()
        

Построим самый простой график при помощи Pylab.

In [2]:
x = linspace(0, 5, 10)
y = x ** 2
figure()
plot(x, y, 'r')
xlabel('x')
ylabel('y')
title('title')
show()

Если покапаться, то можно найти настройки для смены заголовка, подписи осей, сетки и всего остального. Причём LaTex поддерживается из коробки.

In [3]:
fig, ax = plt.subplots()

ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.set_xlabel(r'$\alpha$', fontsize=18)
ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title('title')
ax.legend(loc=2); # upper left corner
Out[3]:
<matplotlib.legend.Legend at 0x9e9de6c>

Несколько графиков строятся похожим на R образом

In [4]:
fig, axes = plt.subplots(nrows=1, ncols=2)

for ax in axes:
    ax.plot(x, y, 'r')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_title('title')
    
fig.tight_layout()

Так же можно строить графики и для временных рядов, используя встроенную функцию пакета Mathplotlib:

In [5]:
#Метод .plot() для Series и DataFrame объектов DataFrame, это всего лишь обёртка для plt.plot:
ts = Series(randn(1000), index=date_range('1/1/2000', periods=1000))
ts = ts.cumsum()
ts.plot();

Можно менять стиль линий и добавить легенду

In [6]:
plt.figure(); ts.plot(style='k--', label='Series'); plt.legend();

А как отобразить несколько графиков на одном рисунке?

In [7]:
#Если применить эту же функцию к DataFrame, она выведет все имеющиеся колонки:
df = DataFrame(randn(1000, 4), index=ts.index, columns=list('ABCD'));
df = df.cumsum();

plt.figure(); 
df.plot(); 
plt.legend(loc='best');
<matplotlib.figure.Figure at 0xa0f77ec>
In [8]:
#Легенда активно по умолчанию. Отключается она при помощи параметра legend=False.
df.plot(legend=False);

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

In [9]:
#Для того, чтобы перейти на логарифмическую шкалу надо задать параметр logy.
plt.figure();
ts = Series(randn(1000), index=date_range('1/1/2000', periods=1000))
ts = np.exp(ts.cumsum())
ts.plot(logy=True);

Также очень легко строится обычный график плотности распределения

In [10]:
ser = Series(randn(1000))
ser.plot(kind='kde');

А теперь посмотрим как в одном наборе данных можно посмотреть на распределение какой-то характеристики для разных классов.

Для этого возьмём данные из одного из открытых курсов по анализу данных.

In [11]:
pData = read_csv('/home/kupa/DataAnalysis/data/ss06pid.csv')
pData['AGEP'].plot(kind='kde', linewidth=3);
pData['AGEP'][pData['SEX'] == 1].plot(kind='kde', linewidth=3, style='orange');

Столбчатые диаграммы

Для данных, в которых можно выделить категории, лучше строить столбчатую диаграмму.

In [12]:
plt.figure();
df.ix[5].plot(kind='bar'); plt.axhline(0, color='k');
In [13]:
change = [23.2, 22.7, 19.7, 13.9, 13.1, 12.8, 12.7,
        12.6, 12.0, 11.5, 10.8, 10.4, 10.4, 9.8, 9.2,
        9.2, 8.8, 7.7, 6.9, 6.9, 6.4, 5.6, 5.3, 5.3, 5.2, 4.9,
        4.8, 4.6, 3.6, 3.1, 0.7, -.3, -.7, -1.2, -1.5, -1.7, 
        -1.7, -1.8, -2, -2.3, -2.4, -3.6, -3.7,
        -4.9, -6.5, -6.6, -11.6, -14.8, -17.6, -23.1]
city = ['Philadelphia', 'Tucson', 'Kansas City, MO',
        'El Paso', 'Portland, Ore.', 'New York', 'Dallas',
        'Columbus', 'Mesa', 'Austin', 'Atlanta', 'Fort Worth',
        'Miami', 'Houston', 'Chicago', 'Oakland', 'Virginia Beach',
        'Baltimore', 'Denver', 'Detroit', 'San Antonio', 'Phoenix',
        'Oklahoma City', 'Indianapolis', 'Milwaukee', 'Sacramento',
        'Washington, D.C.', 'Colorado Springs', 'Honolulu', 'Nashville',
        'Jacksonville', 'Louisville', 'Seattle', 
        'Memphis', 'Fresno', 'Boston', 'Mineappolis',
        'San Jose', 'Tulsa', 'Charlotte', 'San Diego', 'Los Angeles',
        'Long Beach', 'Cleveland', 'San Francisco', 'Albuquerque',
        'Arlington, TX', 'Omaha', 'Wichita', 'Las Vegas']

grad = DataFrame({'change' : change, 'city': city})

plt.figure(figsize=(3, 8))

change = grad.change[grad.change > 0]
city = grad.city[grad.change > 0]
pos = np.arange(len(change))

plt.title('1995-2005 Change in HS graduation rate')
plt.barh(pos, change)

#add the numbers to the side of each bar
for p, c, ch in zip(pos, city, change):
    plt.annotate(str(ch), xy=(ch + 1, p + .5), va='center')

#cutomize ticks
ticks = plt.yticks(pos + .5, city)
xt = plt.xticks()[0]
plt.xticks(xt, [' '] * len(xt))

#minimize chartjunk
remove_border(left=False, bottom=False)
plt.grid(axis = 'x', color ='white', linestyle='-')

#set plot limits
plt.ylim(pos.max(), pos.min() - 1)
plt.xlim(0, 30)
Out[13]:
(0, 30)

Если использовать метод DataFrame.plot() с параметром kind='bar' то получим вот такую картину:

In [14]:
df2 = DataFrame(rand(10, 4), columns=['a', 'b', 'c', 'd']);
df2.plot(kind='bar');
In [15]:
#Чтобы получить пропорциональное распределение значений, используем параметр stacked=True:
df2.plot(kind='bar', stacked=True);
In [16]:
#Чтобы диаграма отображалась горизонтально, используем kind='barh':
df2.plot(kind='barh', stacked=True);

Пример взят из конспекта для гарвардского курса и строится на известном наборе данных пассажиров Титаника.

In [17]:
titanic = read_csv('../data/titanic.csv')
tclass = titanic.groupby(['pclass', 'survived']).size().unstack()
print tclass

red, blue = '#B2182B', '#2166AC'

plt.subplot(121)
plt.bar([0, 1, 2], tclass[0], color=red, label='Died')
plt.bar([0, 1, 2], tclass[1], bottom=tclass[0], color=blue, label='Survived')
plt.xticks([0.5, 1.5, 2.5], ['1st Class', '2nd Class', '3rd Class'], rotation='horizontal')
plt.ylabel("Number")
plt.xlabel("")
plt.legend(loc='upper left')
remove_border()

#normalize each row by transposing, normalizing each column, and un-transposing
tclass = (1. * tclass.T / tclass.T.sum()).T

plt.subplot(122)
plt.bar([0, 1, 2], tclass[0], color=red, label='Died')
plt.bar([0, 1, 2], tclass[1], bottom=tclass[0], color=blue, label='Survived')
plt.xticks([0.5, 1.5, 2.5], ['1st Class', '2nd Class', '3rd Class'], rotation='horizontal')
plt.ylabel("Fraction")
plt.xlabel("")
remove_border()

plt.show()
survived    0    1
pclass            
1st       123  200
2nd       158  119
3rd       528  181

Гистограммы

Надеюсь, что люди, которым нужно объяснять, что такое гистограммы, сейчас закроют этот документ.

In [18]:
plt.figure();
df['A'].diff().hist();
In [19]:
plt.figure();
df['A'].diff().hist(bins=50);
In [20]:
#Для DataFrame'а гистограммы будут отображться вот так:
plt.figure();
df.diff().hist(color='k', alpha=0.5, bins=50);
<matplotlib.figure.Figure at 0xa1a202c>
In [21]:
#Их можно объединять в группы:
data = Series(randn(1000));
data.hist(by=randint(0, 4, 1000), figsize=(6, 4));
<matplotlib.figure.Figure at 0xa96b90c>

Пирожковый график (Pie Chart)

Этот тип графиков отлично подходит для отображения долей, которые принадлежат части данных.

In [22]:
t = titanic.groupby(['pclass']).size()
print t

plt.subplot(aspect=True)
plt.pie(t, labels=t.index.values, colors = dark2_colors[0:3], autopct='%i%%')
plt.title("Passenger Class on the Titanic")
pclass
1st       323
2nd       277
3rd       709
dtype: int64

Out[22]:
<matplotlib.text.Text at 0xa96cf8c>

Box-plot

Как перевести это на русский язык я не представляю, поэтому оставлю оригинальное название. При помощи этого графика можно узнать среднее значение (мат ожидание) и разброс значений (дисперсию) для различных категорий данных. Чёрточки вверху и внизу обозначают максимальное и минимальное значение.

В DataFrame есть встроенный метод boxplot(), который позволяет показать распределение каждой величины.

В качестве примера, возьмём 100 случайных точек, и будем считать те из них, где x < 0 некорректными. На этом графике хорошо видно и среднее значение, и дисперсию и максимальное и минимальное значение для каждой категории.

In [23]:
x = np.random.normal(size=100)
y = np.random.normal(size=100)

y[x < 0] = NaN

tt = DataFrame(zip(x, np.isnan(y)), columns=['x', 'isnan y'])

tt.boxplot(column='x', by='isnan y');
In [24]:
#Можно сделать вот так, сгруппировав по какому-либо признаку
df = DataFrame(rand(10,2), columns=['Col1', 'Col2'] )
df['X'] = Series(['A','A','A','A','A','B','B','B','B','B'])
plt.figure();
bp = df.boxplot(by='X');
<matplotlib.figure.Figure at 0xa41178c>

А можно сразу отрисовывать значения одной характеристики

In [25]:
pData.boxplot(column='AGEP', by='DDRS');

График квантилей (QQ-plot)

Этот график нужен для того, чтобы сравнить распределение случайной величины с каким-то теоретически известным.

В данном примере, функция qqplot из модуля statsmodels позволяет сравнивать тоьлко с распределениями из scipy.stats.distiributions (по умолчаню сравнивается с нормальным стандартным распределением)

In [26]:
from statsmodels.graphics.gofplots import qqplot

x = np.random.normal(size=20)
y = np.random.normal(size=20)

# note: it seems like it's only possible to plot against distributions in scipy.stats.distributions (by default: normal)
qqplot(x, line='45', fit=True);

Scatter график (Scatter plot)

Снова рассмотрим набор данных из примера.

In [27]:
# scatterplot -- size matters
pData.plot(x='JWMNP', y='WAGP', style='o', markersize=3);