Рубрики
Без рубрики

Анализ 1000+ Греческих Вин С Питоном

Вы любите вино ? Вам нравится соскабливать паутину ? Вы любите соскабливать паутину и вино ? В этом посте я буду играть с данными, которые я соскреб из крупного винного интернет-магазина, используя стандартные библиотеки Python.

Автор оригинала: Florents Tselai.

Это было первоначально опубликовано на моем сайте – tselai.com

Одним из моих самых приятных греховных удовольствий всегда было соскабливание паутины . Особенно в течение прошлого года я очистил бесчисленное количество веб-сайтов, как для удовольствия, так и для получения прибыли. От нишевых и основных интернет-магазинов до новостных агентств и литературных блогов удивительно, сколько интересных и чистых данных можно получить с помощью простых инструментов, таких как BeautifulSoup – Я даже не буду упоминать, какой радостью был безголовый режим Chrome.

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

Сам скребок довольно прост, и его можно найти на моей странице GitHub . Здесь я сосредоточусь на быстром исследовательском анализе полученных данных (1125 уникальных меток) с помощью стандартных пакетов python.

Сам скребок предоставляет довольно простой API. Вы запрашиваете данные для данной страницы wine, и она возвращает хороший dict с данными, как это:

from houseofwine_gr import get

get('http://www.houseofwine.gr/how/megas-oinos-skoura.html')
{'ageable': True,
 'alcohol_%': 13.5,
 'avg_rating_%': 82,
 'color': 'Ερυθρός',
 'description': 'Στιβαρό ερυθρό κρασί παλαίωσης. Βασίζεται σε χαρμάνι που παντρεύει το πικάντικο σκέρτσο του Αγιωργίτικου με την αυστηρή δύναμη του Cabernet Sauvignon, στη ζεστή και βανιλάτη αγκαλιά του δρύινου βαρελιού που τα φιλοξένησε κατά την 20μηνη παλαίωσή του. Ποιοτική ετικέτα για μεγάλα φαγοπότια ή εκλεκτά κελάρια.',
 'drink_now': False,
 'keep_2_3_years': False,
 'n_votes': 21,
 'name': 'Μέγας Οίνος Σκούρα 2014',
 'price': 20.9,
 'tags': ['Ήπιος', 'Ξηρός', 'Cabernet Sauvignon', 'Αγιωργίτικο'],
 'url': 'http://www.houseofwine.gr/how/megas-oinos-skoura.html',
 'year': 2014}

Мы начнем с определения некоторых matplotlib эстетики.

%matplotlib inline 
import matplotlib.pyplot as plt 
import matplotlib as mpl
import seaborn as sns 
plt.style.use('fivethirtyeight') 
mpl.rcParams['figure.figsize'] = (8,6)
mpl.rcParams['font.size'] = 14
mpl.rcParams['font.family'] = 'Serif'
mpl.rcParams['figure.facecolor'] = 'white'
plt.rcParams['axes.facecolor']='white'
plt.rcParams['axes.grid'] =False
plt.rcParams['figure.facecolor']='white'

Мы загружаем дамп данных, созданных модулем houseofwine_gr.dump . Вы также можете найти набор данных в .json, .csv и .xlsx на странице GitHub.

import pandas as pd df = pd.read_json('./data/houseofwine.gr-wines.json', encoding='utf-8')

Мы заменяем пустые строки на np.nan , чтобы облегчить их обработку для Панд

from numpy import nan
df = df.replace('', nan, regex=True)

Мы переименовываем некоторые имена столбцов, содержащие специальные символы, чтобы использовать их в качестве собственных Фреймов данных средств доступа.

df = df.rename(columns={'alcohol_%': 'alcohol', 'avg_rating_%': 'avg_rating'}, inplace=False)

Мы также присваиваем столбцам соответствующие типы.

df['alcohol'] = df.alcohol.astype(float)
df['n_votes'] = df.n_votes.astype(int, errors='ignore')
df['price'] = df.price.astype(float)
df['year'] = df.year.astype(int, errors='ignore')

Давайте переведем значения столбцов color с греческого на английский.

df['color'] = df.color.replace({'Λευκός': 'White', 'Ερυθρός': 'Red', 'Ροζέ': 'Rosé' } )

Вот цветовая гистограмма для нашего набора данных.

ax = df['color'].value_counts().plot('bar')
ax.set(ylabel='Wines', title='Wine Color Frequency');
Вот цветовая гистограмма для нашего набора данных.

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

fig, ((ax1, ax2), (ax3, ax4) ) = plt.subplots(ncols=2, nrows=2, figsize=(12,8)) 
df.year.dropna().astype(int).value_counts().plot('bar', ax=ax1)
ax1.set(title='Production Year Frequency', xlabel='Year'); 

sns.distplot(df[df.alcohol < 100].alcohol.dropna(), ax=ax2)
ax2.set(xlabel='Alcohol %', title='Alcohol % Distribution'); 

sns.distplot(df[df.price < 100].price.dropna(), ax=ax3)
ax3.set(xlabel='Price (< 100)') 

sns.distplot(df.avg_rating.dropna(), ax=ax4)
ax4.set(xlabel='Average Rating');
Давайте проверим распределение некоторых простых показателей для каждого вина.

Непосредственным наблюдением является почти нормальное распределение, которое можно увидеть для столбца Средняя оценка с высоким средним значением 85+. Кроутонер на Reddit объясняет, почему это происходит (а также исправляет мою предыдущую ошибку):

Типичная шкала оценки вина составляет 50-100, а не 0-100. Таким образом, то, что выглядело как дистрибутив с поддержкой только половины, на самом деле является почти полностью поддерживаемым дистрибутивом с небольшим левым перекосом. Кроме того, существует огромная разница в культурном отношении между винами с рейтингом ниже и выше 90, причем вина, получившие рейтинг выше 90, обычно считаются значительно лучше, а также продаются значительно лучше. Этот культурный факт полностью меняет разумную интерпретацию данных. Большинство вин оцениваются как хорошие, и только небольшая часть оценивается как действительно хорошие.

Чтобы продвинуться дальше, давайте взглянем на столбец теги .

0 [Riesling, Ήπιος, Ημίγλυκος]
1 [Ήπιος, Sauvignon Blanc, Ξηρός]
2 [Ήπιος, Ξηρός, Ξινόμαυρο, Merlot]
3 [Ήπιος, Ξηρός, Ασύρτικο, Μαλαγουζιά]
4 [Ήπιος, Ξηρός, Pinotage]
5 [Ήπιος, Sangiovese, Ξηρός]
6 [Ήπιος, Ξηρός, Ξινόμαυρο]
7 [Cabernet Sauvignon, Ήπιος, 13, Ξηρός, Merlot,...
8 [Ήπιος, Γλυκός]
9 [Ήπιος, Αηδάνι, Ξηρός]
Name: tags, dtype: object

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

Сначала мы преобразуем теги элементы столбцов из списка в набор , так как это облегчит манипуляции. То есть вместо того, чтобы запутываться в if x in-else-try-except-IndexError мы будем использовать set операции. Скажите, что вы хотите о пределе в 79 символов и удобочитаемости; в интерактивном режиме однострочные и неизменяемые операции.

df['tags'] = df.tags.map(set)

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

sweetness_values = {'Γλυκός', 'Ημίγλυκος', 'Ξηρός', 'Ημίξηρος'}
df['sweetness'] = df.tags.map(sweetness_values.intersection).map(lambda x: x.pop() if x else None) 

translations = {'Γλυκός': 'Sweet', 'Ημίγλυκος': 'Semi-Sweet', 'Ξηρός': 'Dry', 'Ημίξηρος': 'Semi-Dry'}
df['sweetness'] = df['sweetness'].replace(translations)
df['sparkling'] = df.tags.map({'Αφρώδης', 'Ημιαφρώδης'}.intersection ).map(lambda x: x.pop() if x else None ).replace({'Αφρώδης': 'Sparkling', 'Ημιαφρώδης': 'Semi-Sparkling'})
df['sparkling'] = df.sparkling.fillna('Not Sparkling')

df['is_mild'] = df.tags.map(lambda x: 'Ήπιος' in x)

Вот гистограмма для каждого из этих 4 атрибутов.

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, sharex=True, squeeze=False) for attr, ax in zip(['sweetness', 'color', 'sparkling', 'is_mild'], (ax1, ax2, ax3, ax4)): df[attr].value_counts().sort_values(ascending=True).plot(kind='barh', ax=ax) attr_str = 'Mildness' if attr is 'is_mild' else attr.title() ax.set(xlabel='Number of Wines', title='{} Frequency'.format(attr_str)); fig.set_size_inches((12,8))
fig.tight_layout()
Вот гистограмма для каждого из этих 4 атрибутов.

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

non_varietal_tags = {'Αφρώδης', 'Ημιαφρώδης', 'Ξηρός', 'Ημίξηρος', 'Γλυκός', 'Ημίγλυκος', 'Ήπιος'}
df['varieties'] = df.tags.map(lambda t: t.difference(non_varietal_tags))

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

def is_not_int(x):
    try:
        int(x)
        return False
    except ValueError:
        return True
    
df['varieties'] = df.varieties.map(lambda x: set(filter(is_not_int, x)))

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

df['is_varietal'] = df.varieties.map(set. __len__ ) == 1

Для сортовых вин, в частности, мы устанавливаем single_variety – это значение будет NaN для остальных не сортовых вин.

df.loc[df.is_varietal, 'single_variety'] = df.loc[df.is_varietal, 'varieties'].map(lambda v: next(iter(v)))
df['is_blend'] = df.varieties.map(set. __len__ ) >= 2
df.loc[df.is_varietal, 'variety_type'] = 'Varietal'
df.loc[df.is_blend, 'variety_type'] = 'Blend'

Давайте посмотрим, как выглядит распределение сортов/смесей.

ax = df.is_varietal.replace({True: 'Varietal', False: 'Blend'} ).value_counts().plot('barh')
ax.set(title='How many wines are varietals and how many blends ?');
Давайте посмотрим, как выглядит распределение сортов/смесей.

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

Вот несколько показательных сюжетов.

fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(12,14))
fig.tight_layout varieties_hist = df[df.is_varietal].varieties.map(lambda x: next(iter(x))).value_counts()

varieties_hist.head(10).sort_values(ascending=True).plot('barh', ax=ax1)
ax1.set(title='Most Frequent Varietal Wines (Top 10)'); 
varietals_mean_price = df[['single_variety', 'price']].groupby('single_variety').mean().dropna()['price']

varietals_mean_price.sort_values(ascending=False).head(10).sort_values(ascending=True).plot('barh', ax=ax2)

ax2.set(title='Most Expensive Varietals', xlabel='', ylabel=''); varietals_mean_rating = df[['single_variety', 'avg_rating']].groupby('single_variety').mean().dropna()['avg_rating']

varietals_mean_rating.sort_values(ascending=False).head(10).sort_values(ascending=True).plot('barh', ax=ax3);
ax3.set(title='Top Rated Varietals', ylabel=''); fig.tight_layout()
Вот несколько показательных сюжетов.

Похоже, Шардоне является самым популярным сортом, в то время как Видаль и Санджовезе являются самыми дорогими. Самый рейтинговый – Мальвазия , но все разновидности довольно близки.

Переключив наше внимание на смеси, мы делаем некоторые Numpy и Scikit-Learn magic, чтобы создать матрицу совпадений разновидностей, появляющихся в смесях.

def create_coocurrence_df(docs):
    # Source: https://stackoverflow.com/a/37822989
    count_model = CountVectorizer(lowercase=False, min_df=.1) # default unigram model
    X = count_model.fit_transform(docs)
    Xc = (X.T * X) # this is co-occurrence matrix in sparse csr format
    Xc.setdiag(0) # sometimes you want to fill same word cooccurence to 0
    ret = pd.DataFrame(Xc.todense())#, index=count_model.get_feature_names(), columnscou=count_model.get_feature_names())
    ret.index = ret.columns = list(map(lambda f: f.replace('_', ' '), count_model.get_feature_names()))
    return ret
from sklearn.feature_extraction.text import CountVectorizer
docs = df.loc[df.is_blend, 'varieties'].map(lambda x: {s.replace(' ', '_') for s in x}).map(lambda x: ' '.join(x)) 
coocurrence = create_coocurrence_df(docs)

Это те сорта, которые чаще всего появляются в смесях.

ax = coocurrence.sum().sort_values(ascending=False).head(10).sort_values(ascending=True).plot(kind='barh')
ax.set(title='Most Frequent Varieties Appearing in Blends (Top 10)');
Это те сорта, которые чаще всего появляются в смесях.

А вот тепловая карта, показывающая, какие сорта обычно смешиваются вместе.

ax = sns.heatmap(coocurrence, square=True, annot=True, fmt="d", cmap='Blues')
ax.set(title='Which varieties are usually blended together ?\n'.title());
plt.gcf().set_size_inches((8,6))
А вот тепловая карта, показывающая, какие сорта обычно смешиваются вместе.
fig, axes = plt.subplots(nrows=3, figsize=(12, 18))
for c, ax in zip(['Red', 'White', 'Rosé'], axes):
    docs = df.loc[df.is_blend & (df.color==c), 'varieties'].map(lambda x: {s.replace(' ', '_') for s in x}).map(lambda x: ' '.join(x))
    cooc = create_coocurrence_df(docs)
    cmaps = {'Red': 'Reds', 'White': 'Blues', 'Rosé': sns.cubehelix_palette(8)}
    sns.heatmap(cooc, square=True, annot=True, fmt="d", cmap=cmaps.get(c), ax=ax)
    ax.set(title='Usually blended varieties for {}\n'.format(c).title());
plt.tight_layout()
А вот тепловая карта, показывающая, какие сорта обычно смешиваются вместе.

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

Это было первоначально опубликовано на моем сайте – tselai.com