Начальный анализ данных, часть 2 (Exploratory Analysis, part 2)

Этот пост является переводом и обощением информации, которую приводит автор этого конспекта.

В ней рассматривается набор данных olive.csv, взятый из книги Forina, M., Armanino, C., Lanteri, S. & Tiscornia, E. (1983), Classification of Olive Oils from their Fatty Acid Composition, in Martens, H. and Russwurm Jr., H., eds, Food Research and Data Analysis, Applied Science Publishers, London, pp. 189

Этот набор данных содержит 8 характеристик (уровни кислот в оливках), распределённых по 9 классам, которые представляют собой регионы Италии, где их собрали. Цель проводимого анализа состоит в том, чтобы найти способы определения происхождения оливок. Эта задача появилась исходя из практического интереса, потому что оливковое масло из некоторых районов ценится выше, чем из других.

Представлено 572 измерения Для каждого объекта задано 10 свойств.

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

In [33]:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.colors as colors
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'
In [34]:
df=pd.read_csv("../data/olive.csv")
df.head(5)
Out[34]:
Region Area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
0 South North Apulia 1075 75 226 7823 672 36 60 29
1 South North Apulia 1088 73 224 7709 781 31 61 29
2 South North Apulia 911 54 246 8113 549 31 63 29
3 South North Apulia 966 57 240 7952 619 50 78 35
4 South North Apulia 1051 67 259 7771 672 50 80 46

Для начала мы переименуем первую колонку

Hint: Для того, чтобы узнать как это делается достаточно загуглить 'python pandas dataframe rename', что приведёт на эту страничку документации.

In [35]:
print df.columns
df.rename(columns={df.columns[0]:'region',df.columns[1]:'area'}, inplace=True)
df.columns
Index([u'Region', u'Area', u'palmitic', u'palmitoleic', u'stearic', u'oleic', u'linoleic', u'linolenic', u'arachidic', u'eicosenoic'], dtype=object)

Out[35]:
Index([u'region', u'area', u'palmitic', u'palmitoleic', u'stearic', u'oleic', u'linoleic', u'linolenic', u'arachidic', u'eicosenoic'], dtype=object)

Посмотрим на имеющиеся данные. Найдём, какие у нас бывают виды регионов и областей.

In [36]:
print 'regions\t', df.region.unique()
print 'areas\t', df.area.unique()
regions	['South' 'Sardinia' 'North']
areas	['North Apulia' 'Calabria' 'South Apulia' 'Sicily' 'Inland Sardinia'
 'Coastal Sardinia' 'Umbria' 'East Liguria' 'West Liguria']

Посмотрим на результат перекрёстного анализа региона и области. Для этого мы воспользуемся функцией .crosstab()

In [37]:
pd.crosstab(df.area, df.region)
Out[37]:
region North Sardinia South
area
Calabria 0 0 56
Coastal Sardinia 0 33 0
East Liguria 50 0 0
Inland Sardinia 0 65 0
North Apulia 0 0 25
Sicily 0 0 36
South Apulia 0 0 206
Umbria 51 0 0
West Liguria 50 0 0

Вот так вот можно выбрать 2 отдельные колонки:

In [38]:
df[['palmitic','oleic']].head()
Out[38]:
palmitic oleic
0 1075 7823
1 1088 7709
2 911 8113
3 966 7952
4 1051 7771

Значения одной колонки можно посмотреть при помощи следующей команды:

In [39]:
df.palmitic
Out[39]:
0     1075
1     1088
2      911
3      966
4     1051
5      911
6      922
7     1100
8     1082
9     1037
10    1051
11    1036
12    1074
13     875
14     952
...
557    1010
558    1020
559    1120
560    1090
561    1100
562    1090
563    1150
564    1110
565    1010
566    1070
567    1280
568    1060
569    1010
570     990
571     960
Name: palmitic, Length: 572, dtype: int64

А сейчас мы переведём значения в более привычные доли в новой переменной dfsub. Для этого мы воспользуемся пайтоновской функцией apply()

In [40]:
acidlist=['palmitic', 'palmitoleic', 'stearic', 'oleic', 'linoleic', 'linolenic', 'arachidic', 'eicosenoic']

dfsub=df[acidlist].apply(lambda x: x/100.0)
dfsub.head()
Out[40]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
0 10.75 0.75 2.26 78.23 6.72 0.36 0.60 0.29
1 10.88 0.73 2.24 77.09 7.81 0.31 0.61 0.29
2 9.11 0.54 2.46 81.13 5.49 0.31 0.63 0.29
3 9.66 0.57 2.40 79.52 6.19 0.50 0.78 0.35
4 10.51 0.67 2.59 77.71 6.72 0.50 0.80 0.46
In [41]:
df[acidlist]=dfsub
df.head()
Out[41]:
region area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
0 South North Apulia 10.75 0.75 2.26 78.23 6.72 0.36 0.60 0.29
1 South North Apulia 10.88 0.73 2.24 77.09 7.81 0.31 0.61 0.29
2 South North Apulia 9.11 0.54 2.46 81.13 5.49 0.31 0.63 0.29
3 South North Apulia 9.66 0.57 2.40 79.52 6.19 0.50 0.78 0.35
4 South North Apulia 10.51 0.67 2.59 77.71 6.72 0.50 0.80 0.46

Теперь просто посмотрим на распредления кислот в нашем массиве при помощи стандартных функций. Мат ожидание, стандартное отклонение, дисперсия.

In [42]:
df.palmitic.mean(), df.palmitic.std(), df.palmitic.var()
Out[42]:
(12.317412587412589, 1.6859226405632786, 2.8423351499638576)
In [43]:
df.palmitic.std(), np.sqrt(df.palmitic.var())
Out[43]:
(1.6859226405632786, 1.6859226405632786)
In [44]:
np.std(df.palmitic), np.mean(df.palmitic), np.var(df.palmitic)
Out[44]:
(1.6844482872943343, 12.317412587412589, 2.8373660325688159)
In [45]:
np.sqrt(np.var(df.palmitic))
Out[45]:
1.6844482872943343

Сейчас мы посмотрим на кореллированность кислот между собой. Высокая корреляция обусловлена наличием какой-то взаимосвязи между переменными.

In [46]:
df[acidlist].corr()
Out[46]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
palmitic 1.000000 0.835605 -0.170392 -0.837335 0.460684 0.319327 0.228299 0.501952
palmitoleic 0.835605 1.000000 -0.222185 -0.852438 0.621627 0.093112 0.085481 0.416350
stearic -0.170392 -0.222185 1.000000 0.113599 -0.197817 0.018917 -0.040979 0.140377
oleic -0.837335 -0.852438 0.113599 1.000000 -0.850318 -0.218171 -0.319962 -0.424146
linoleic 0.460684 0.621627 -0.197817 -0.850318 1.000000 -0.057439 0.210973 0.089045
linolenic 0.319327 0.093112 0.018917 -0.218171 -0.057439 1.000000 0.620236 0.578319
arachidic 0.228299 0.085481 -0.040979 -0.319962 0.210973 0.620236 1.000000 0.328663
eicosenoic 0.501952 0.416350 0.140377 -0.424146 0.089045 0.578319 0.328663 1.000000

Теперь построим ковариационную матрицу.

In [47]:
df[acidlist].cov()
Out[47]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
palmitic 2.842335 0.739522 -0.105556 -5.728752 1.885769 0.069818 0.084793 0.119180
palmitoleic 0.739522 0.275566 -0.042857 -1.815928 0.792300 0.006339 0.009886 0.030781
stearic -0.105556 -0.042857 0.135019 0.169392 -0.176485 0.000901 -0.003317 0.007264
oleic -5.728752 -1.815928 0.169392 16.468194 -8.378221 -0.114820 -0.286050 -0.242406
linoleic 1.885769 0.792300 -0.176485 -8.378221 5.895146 -0.018086 0.112848 0.030448
linolenic 0.069818 0.006339 0.000901 -0.114820 -0.018086 0.016819 0.017720 0.010563
arachidic 0.084793 0.009886 -0.003317 -0.286050 0.112848 0.017720 0.048533 0.010197
eicosenoic 0.119180 0.030781 0.007264 -0.242406 0.030448 0.010563 0.010197 0.019834

А также оценим насколько медианы отличаются от математического ожидания. Как правило это происходит, когда есть большое смещение в распределении величины.

In [48]:
df[acidlist].median(), df[acidlist].mean()
Out[48]:
(palmitic       12.010
palmitoleic     1.100
stearic         2.230
oleic          73.025
linoleic       10.300
linolenic       0.330
arachidic       0.610
eicosenoic      0.170
dtype: float64,
 palmitic       12.317413
palmitoleic     1.260944
stearic         2.288654
oleic          73.117483
linoleic        9.805280
linolenic       0.318881
arachidic       0.580979
eicosenoic      0.162815
dtype: float64)

В данном случае его нет.

Графики

Теперь построим немного графиков. Какие они бывают, я уже переводил до этого.

In [49]:
fig=plt.figure()
plt.scatter(df.palmitic, df.linolenic)
axis = fig.gca() #get current axis
axis.set_title('linolenic vs palmitic')
axis.set_xlabel('palmitic')
axis.set_ylabel('linolenic')
#ax can be got with fig.gca()
Out[49]:
<matplotlib.text.Text at 0xa97314c>
In [50]:
plt.hist(df.palmitic)
Out[50]:
(array([   1.,    0.,   11.,   71.,  188.,   79.,  131.,   73.,    9.,    9.]),
 array([  6.1  ,   7.243,   8.386,   9.529,  10.672,  11.815,  12.958,
        14.101,  15.244,  16.387,  17.53 ]),
 <a list of 10 Patch objects>)

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

In [51]:
fig, axes=plt.subplots(figsize=(10,10), nrows=2, ncols=2)
axes[0][0].plot(df.palmitic, df.linolenic)
axes[0][1].plot(df.palmitic, df.linolenic, '.')
axes[1][0].scatter(df.palmitic, df.linolenic)
axes[1][1].hist(df.palmitic)
fig.tight_layout()

Группировка и выборка данных в pandas

In [52]:
region_groupby = df.groupby('region');
print type(region_groupby);
region_groupby.head()
<class 'pandas.core.groupby.DataFrameGroupBy'>

Out[52]:
region area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 421 North Umbria 10.85 0.70 1.80 79.55 6.05 0.20 0.50 0.01
422 North Umbria 10.85 0.70 1.85 79.55 6.00 0.25 0.55 0.01
423 North Umbria 10.90 0.60 1.90 79.50 6.00 0.28 0.47 0.02
424 North Umbria 10.80 0.65 1.89 79.60 6.02 0.35 0.20 0.01
425 North Umbria 10.90 0.60 1.95 79.55 6.00 0.28 0.42 0.02
Sardinia 323 Sardinia Inland Sardinia 11.29 1.20 2.22 72.72 11.12 0.43 0.98 0.02
324 Sardinia Inland Sardinia 10.42 1.35 2.10 73.76 11.16 0.35 0.90 0.03
325 Sardinia Inland Sardinia 11.03 0.96 2.10 73.80 10.85 0.32 0.94 0.03
326 Sardinia Inland Sardinia 11.18 0.97 2.21 72.79 11.54 0.35 0.94 0.02
327 Sardinia Inland Sardinia 10.52 0.95 2.15 73.88 11.26 0.31 0.92 0.01
South 0 South North Apulia 10.75 0.75 2.26 78.23 6.72 0.36 0.60 0.29
1 South North Apulia 10.88 0.73 2.24 77.09 7.81 0.31 0.61 0.29
2 South North Apulia 9.11 0.54 2.46 81.13 5.49 0.31 0.63 0.29
3 South North Apulia 9.66 0.57 2.40 79.52 6.19 0.50 0.78 0.35
4 South North Apulia 10.51 0.67 2.59 77.71 6.72 0.50 0.80 0.46

Функция groupby() возвращает словарь, в котором значения факторных переменных являются ключами.

In [53]:
for key, value in region_groupby:
    print "( key, type(value) ) = (", key, ",", type(value), ")"
    v=value

v.head()
( key, type(value) ) = ( North , <class 'pandas.core.frame.DataFrame'> )
( key, type(value) ) = ( Sardinia , <class 'pandas.core.frame.DataFrame'> )
( key, type(value) ) = ( South , <class 'pandas.core.frame.DataFrame'> )

Out[53]:
region area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
0 South North Apulia 10.75 0.75 2.26 78.23 6.72 0.36 0.60 0.29
1 South North Apulia 10.88 0.73 2.24 77.09 7.81 0.31 0.61 0.29
2 South North Apulia 9.11 0.54 2.46 81.13 5.49 0.31 0.63 0.29
3 South North Apulia 9.66 0.57 2.40 79.52 6.19 0.50 0.78 0.35
4 South North Apulia 10.51 0.67 2.59 77.71 6.72 0.50 0.80 0.46

А сейчас немного небольшой питоновской магии. Фишка в том, объект, возвращаемый функцией groupby может быть вызван функцией map. В результате это сильно упрощает начальую обработку данных.

In [54]:
dfrd=region_groupby.describe()
print type(dfrd)
dfrd.head(20)
<class 'pandas.core.frame.DataFrame'>

Out[54]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North count 151.000000 151.000000 151.000000 151.000000 151.000000 151.000000 151.000000 151.000000
mean 10.948013 0.837351 2.308013 77.930530 7.270331 0.217881 0.375762 0.019735
std 0.825635 0.264388 0.389560 1.648155 1.431226 0.168865 0.293586 0.007298
min 6.100000 0.150000 1.700000 73.400000 5.100000 0.000000 0.000000 0.010000
25% 10.600000 0.690000 2.000000 76.800000 6.020000 0.100000 0.100000 0.010000
50% 10.900000 0.800000 2.300000 78.000000 6.800000 0.200000 0.380000 0.020000
75% 11.250000 1.000000 2.500000 79.500000 8.250000 0.350000 0.595000 0.025000
max 14.000000 1.800000 3.500000 84.100000 10.500000 0.700000 1.000000 0.030000
Sardinia count 98.000000 98.000000 98.000000 98.000000 98.000000 98.000000 98.000000 98.000000
mean 11.113469 0.967449 2.261837 72.680204 11.965306 0.270918 0.731735 0.019388
std 0.404111 0.138514 0.176363 1.418783 1.072336 0.053844 0.118826 0.007436
min 10.300000 0.350000 1.990000 68.820000 10.570000 0.150000 0.450000 0.010000
25% 10.852500 0.882500 2.120000 71.372500 11.122500 0.230000 0.660000 0.010000
50% 11.075000 0.960000 2.220000 73.255000 11.465000 0.270000 0.720000 0.020000
75% 11.372500 1.040000 2.395000 73.810000 13.065000 0.300000 0.810000 0.020000
max 12.130000 1.350000 2.720000 74.390000 14.700000 0.430000 1.050000 0.030000
South count 323.000000 323.000000 323.000000 323.000000 323.000000 323.000000 323.000000 323.000000
mean 13.322879 1.548019 2.287740 71.000093 10.334985 0.380650 0.631176 0.273220
std 1.529349 0.507237 0.398709 3.451431 2.106730 0.079727 0.111644 0.083915
min 8.750000 0.350000 1.520000 63.000000 4.480000 0.200000 0.320000 0.100000

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

In [55]:
vecs=[]
keys=[]
for key, value in region_groupby:
    k=key
    v=value.std()
print k, type(v), v
South <class 'pandas.core.series.Series'> palmitic       1.529349
palmitoleic    0.507237
stearic        0.398709
oleic          3.451431
linoleic       2.106730
linolenic      0.079727
arachidic      0.111644
eicosenoic     0.083915
dtype: float64

Или например мы можем воспользоваться магией pandas, которая сама запихает все объекты, к которым мы применим функцию std() обратно в DataFrame.

In [56]:
dfbystd=df.groupby('region').std()
dfbystd.head()
Out[56]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 0.825635 0.264388 0.389560 1.648155 1.431226 0.168865 0.293586 0.007298
Sardinia 0.404111 0.138514 0.176363 1.418783 1.072336 0.053844 0.118826 0.007436
South 1.529349 0.507237 0.398709 3.451431 2.106730 0.079727 0.111644 0.083915

Или мы можем использовать функцию aggregate(), чтобы применять функцию к значениям столбцов

In [57]:
dfbymean=region_groupby.aggregate(np.mean)
dfbymean.head()
Out[57]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 10.948013 0.837351 2.308013 77.930530 7.270331 0.217881 0.375762 0.019735
Sardinia 11.113469 0.967449 2.261837 72.680204 11.965306 0.270918 0.731735 0.019388
South 13.322879 1.548019 2.287740 71.000093 10.334985 0.380650 0.631176 0.273220
In [58]:
region_groupby.aggregate(lambda x: x.palmitic.sum()) 
Out[58]:
area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 1653.15 1653.15 1653.15 1653.15 1653.15 1653.15 1653.15 1653.15 1653.15
Sardinia 1089.12 1089.12 1089.12 1089.12 1089.12 1089.12 1089.12 1089.12 1089.12
South 4303.29 4303.29 4303.29 4303.29 4303.29 4303.29 4303.29 4303.29 4303.29

Точно также можно использовать apply()

In [59]:
region_groupby.apply(lambda f: f.mean())
Out[59]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 10.948013 0.837351 2.308013 77.930530 7.270331 0.217881 0.375762 0.019735
Sardinia 11.113469 0.967449 2.261837 72.680204 11.965306 0.270918 0.731735 0.019388
South 13.322879 1.548019 2.287740 71.000093 10.334985 0.380650 0.631176 0.273220
In [60]:
region_groupby.apply(lambda f: f.palmitic.mean())
Out[60]:
region
North       10.948013
Sardinia    11.113469
South       13.322879
dtype: float64

Теперь добавим парочку колонок

In [61]:
renamedict_std={k:k+"_std" for k in acidlist}
renamedict_mean={k:k+"_mean" for k in acidlist}
dfbystd.rename(inplace=True, columns=renamedict_std)
dfbymean.rename(inplace=True, columns=renamedict_mean) 
dfbystd.head()
Out[61]:
palmitic_std palmitoleic_std stearic_std oleic_std linoleic_std linolenic_std arachidic_std eicosenoic_std
region
North 0.825635 0.264388 0.389560 1.648155 1.431226 0.168865 0.293586 0.007298
Sardinia 0.404111 0.138514 0.176363 1.418783 1.072336 0.053844 0.118826 0.007436
South 1.529349 0.507237 0.398709 3.451431 2.106730 0.079727 0.111644 0.083915

Внутри pandas предусмотрена возможность для объединения данных, прям как в SQL

In [62]:
dfpalmiticmean = dfbymean[['palmitic_mean']] 
dfpalmiticstd = dfbystd[['palmitic_std']] 

newdfbyregion=dfpalmiticmean.join(dfpalmiticstd)
newdfbyregion.head()
Out[62]:
palmitic_mean palmitic_std
region
North 10.948013 0.825635
Sardinia 11.113469 0.404111
South 13.322879 1.529349

Одномерный обзорный анализ (One Dimensional Exploratory Data Analysis (EDA) with Pandas)

In [63]:
rkeys=[1,2,3]
rvals=['South','Sardinia','North']
rmap={e[0]:e[1] for e in zip(rkeys,rvals)}
rmap
Out[63]:
{1: 'South', 2: 'Sardinia', 3: 'North'}

Сделаем отдельный набор данных, содержащий данные только о кислотах.

In [64]:
mdf2=df.groupby('region').aggregate(np.mean)
mdf2=mdf2[acidlist]
mdf2.head()
Out[64]:
palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
region
North 10.948013 0.837351 2.308013 77.930530 7.270331 0.217881 0.375762 0.019735
Sardinia 11.113469 0.967449 2.261837 72.680204 11.965306 0.270918 0.731735 0.019388
South 13.322879 1.548019 2.287740 71.000093 10.334985 0.380650 0.631176 0.273220

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

In [65]:
ax=mdf2.plot(kind='barh', stacked=True)
ax.set_yticklabels(rvals)
ax.set_xlim([0,100])
Out[65]:
(0, 100)

А теперь построим распределение каждой кислоты по регионам

In [66]:
ig, axes=plt.subplots(figsize=(10,20), nrows=len(acidlist), ncols=1)
i=0
for ax in axes.flatten():
    acid=acidlist[i]
    seriesacid=df[acid]#get the Pandas series
    minmax=[seriesacid.min(), seriesacid.max()]
    for k,g in df.groupby('region'):
        style = {'histtype':'stepfilled', 'alpha':0.5, 'label':k, 'ax':ax}
        g[acid].hist(**style)
        ax.set_xlim(minmax)
        ax.set_title(acid)
        ax.grid(False)
    #construct legend
    ax.legend()
    i=i+1
fig.tight_layout()

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

In [67]:
mask=(df.eicosenoic < 0.05)
np.sum(mask), np.mean(mask)
Out[67]:
(249, 0.43531468531468531)

А теперь используем условную выборку для DataFrame:

In [68]:
loweico=df[df.eicosenoic < 0.02]
pd.crosstab(loweico.area, loweico.region)
Out[68]:
region North Sardinia
area
Coastal Sardinia 0 11
East Liguria 17 0
Inland Sardinia 0 19
Umbria 14 0
West Liguria 11 0

Двумерный обзорный анализ (Two-dimensional EDA with Pandas)

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

In [69]:
mycolors=['#348ABD', '#A60628', '#7A68A6', '#467821','#D55E00',  '#CC79A7', 
           '#56B4E9', '#009E73', '#F0E442']
cmap=colors.ListedColormap(mycolors)
a=np.outer(np.arange(0,1,0.01),np.ones(10))
plt.imshow(a.T, cmap=cmap, origin="lower");
In [70]:
def make2d_eda(df, scatterx, scattery, by="region", labeler={}):
    figure=plt.figure(figsize=(8,8))
    ax=plt.gca()
    cs=list(np.linspace(0,1,len(df.groupby(by))))
    xlimsd={}
    ylimsd={}
    xs={}
    ys={}
    for k,g in df.groupby(by):
        col=cs.pop()
        x=g[scatterx]
        y=g[scattery]
        c=cmap(col)
        ax.scatter(x, y, c=c, label=labeler.get(k,k), s=40, alpha=0.4);
        xlimsd[k]=ax.get_xlim()
        ylimsd[k]=ax.get_ylim()
    xlims=[min([xlimsd[k][0] for k in xlimsd]), max([xlimsd[k][1] for k in xlimsd])]
    ylims=[min([ylimsd[k][0] for k in ylimsd]), max([ylimsd[k][1] for k in ylimsd])]
    ax.set_xlim(xlims)
    ax.set_ylim(ylims)
    ax.set_xlabel(scatterx)
    ax.set_ylabel(scattery)
    ax.grid(False)
    return ax
a=make2d_eda(df, "linoleic","arachidic", labeler=rmap)
a.legend(loc='upper right');

Из этого графика видно, что можно линейно отделить регион Сардинии от Северной части.

А теперь построим матрицу рассеяния, известную из прошлой статьи

In [71]:
from pandas.tools.plotting import scatter_matrix
scatter_matrix(df[['linoleic','arachidic','eicosenoic']], alpha=0.3, figsize=(10, 10), diagonal='kde');
In [72]:
acidlistminusoleic=['palmitic', 'palmitoleic', 'stearic', 'linoleic', 'linolenic', 'arachidic', 'eicosenoic']
rkeys=[1,2,3]
rvals=['South','Sardinia','North']
reversemap={e[0]:e[1] for e in zip(rvals,rkeys)}
plt.figure(figsize=(12,20))
for key, group in df.groupby('region'):
    plt.subplot(int('31'+str(reversemap[key])))
    group[acidlistminusoleic].boxplot(grid=False)
    ax=plt.gca()
    ax.set_title(rvals[reversemap[key]-1])
    ax.grid(axis="y", color="gray", linestyle=':', lw=1)