День святого Валентина — одна из моих любимых дат в году. 14 февраля любовь везде: тысячи разных открыток на полках Walgreens, летающие воздушные шары в форме сердечек, люди с цветами в метро, ​​милые парочки в кафе… Определенно, не лучший день для этого. одинокий, особенно в городе любви. В этой статье используются геопространственные данные, чтобы помочь вам выбрать лучшее кафе для свидания в Париже (код Python в конце!!!).

Мне очень нравится ходить в кафе, пекарни, кофейни, места для бранча... Не знаю, из-за атмосферы, запаха свежего кофе или меню завтрака в течение всего дня, но эти места иметь все мое сердце. На самом деле, употребление кофе улучшает кровообращение, помогая мелким кровеносным сосудам работать лучше и укрепляя сердечно-сосудистую систему, или, другими словами, помогая вам лучше любить. Может быть, поэтому кафе кажутся мне такими романтичными, а те, что в городе любви... très romantique!

В Париже много ресторанов. Если рассматривать нефильтрованные данные TripAdvisor, то в городе более 17 тысяч ресторанов (!!!). Это означает примерно 163 ресторана на км² или 8 ресторанов на каждую 1000 жителей. Я живу в Сан-Паулу, население здесь в 6 раз больше, а соотношение ресторанов к 1000 жителей составляет около 1. С таким количеством вариантов я должен быть прагматичным, систематическим и программировать, чтобы найти лучшее место.

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

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

  • Квантиль от 0% до 5%: 5 звезд
  • Квантиль от 5% до 40%: 4 звезды
  • Квантиль от 40% до 60%: 3 звезды
  • Квантиль от 60% до 80%: 2 звезды
  • Квантиль от 80% до 100%: 1 звезда

Конечно, мы ищем места с лучшими отзывами, поэтому имеет смысл ограничить выборку 5-звездочными ресторанами, но мы также должны учитывать очень важную переменную: количество отзывов. Обычно есть золотая середина между хорошими отзывами и большим количеством отзывов. Чем больше людей посещают это место, тем выше вероятность того, что кто-то оставит плохой отзыв. Кроме того, люди обычно больше жалуются, чем хвалят. С другой стороны, отличные рестораны, как правило, получают много отзывов только потому, что они единодушны… Так что здесь я должен быть осторожен.

Если вы (как и я) никогда не были в Париже, город разделен на 20 округов, как вы можете видеть на диаграмме ниже. По всему Парижу есть туристические места для посещения — например, Эйфелева башня находится на 7-м, Лувр — на 1-м, а Пантеон — на 5-м — поэтому мы ожидаем увидеть рестораны, разбросанные по всей карте.

Конечно, меня не интересуют никакие рестораны. Я хочу кафе. Поэтому я отфильтровал данные TripAdvisor, чтобы включить только места, классифицированные как «Кафе» — 454 места. Как и положено, большинство заведений классифицируются как «французские», но мы также находим американские заведения и даже кафе быстрого питания (см. таблицу ниже).

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

В 20-м округе, Менильмонтане, наименьшее количество мест, 20 из 454, тогда как в 1-м, Лувре, наблюдается наибольшая концентрация кафе в Париже, 50 из 454, хотя это один из самых маленьких округов.

Конечно, количество не обязательно означает качество, но чем больше выборка отзывов, тем больше шансов найти идеальное 5-звездочное место. На самом деле, в 1-м округе есть 3 5-звездочных кафе, а в 20-м нет ни одного. Лучшее (кафе) — 4-е, ровно 4 5-звездочных кафе.

На приведенной ниже диаграмме показано количество отзывов по округам, и, как и ожидалось, более популярные места, такие как округа 1, 6 и 7, имеют много отзывов.

Хорошо, вы можете ожидать найти хорошие кафе в районе 1-го и 6-го числа, но более внимательно изучив данные, я на самом деле нашел свою идеальную топ-3 где-то в другом месте. В этом вся прелесть данных l̶o̶v̶e̶. Если предположить, что 100 — это разумное количество отзывов, идеальным кафе для свидания в Париже будет Jozi Cafe в 5-м округе. Если вместо этого установить порог равный 1000, то наше место — Бертийон в 4-м округе. Я определенно больше похожа на девушку со 100 отзывами.

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

import geoplot as gplt
import geopandas as gpd
import geoplot.crs as gcrs
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# PARIS ARRONDISSEMENTS
paris = gpd.read_file(r'arrondissements.shp')
paris['l_aroff'] = paris['l_aroff'].str.replace('é','é')
paris['l_aroff'] = paris['l_aroff'].str.replace('ô','ô')
paris['l_aroff'] = paris['l_aroff'].str.replace('É','é')
paris['l_aroff'] = paris['l_aroff'].str.capitalize()
paris['l_ar'] = paris['l_ar'].str.replace('ème Ardt','')
paris['l_ar'] = paris['l_ar'].str.replace('er Ardt','')

# TRIPADVISOR'S DATA
df = gpd.read_file(r'dataset_tripadvisor-restaurants-scraper_2023-01-19_17-58-37-683.csv')
df = df.replace('',np.nan)
df.reset_index(inplace=True)
df['geometry'] = gpd.points_from_xy(df['longitude'],df['latitude'])
df['cuisine'] = df['cuisine/0'] +'-'+ df['cuisine/1']

df['l_ar'] = np.nan   # including arrondissement information in dataframe
for i in df.index:
    idx = paris.contains(df['geometry'].loc[i])
    if idx.sum()>0:
        df.loc[i,'l_ar'] = paris['l_ar'][idx].values[0]

columns = [
    'id',
    'name',
    'cuisine', 
    'rankingPosition',
    'numberOfReviews',
    'l_ar',
    'address',
    'latitude',
    'longitude',
    'geometry']

df = df[columns].dropna()
df['rankingPosition'] = df['rankingPosition'].astype(int)
df['numberOfReviews'] = df['numberOfReviews'].astype(int)
df = df[df['numberOfReviews']>1]

# BUILDING STARS RULE
bins = [0, 
        df['rankingPosition'].quantile(0.10), 
        df['rankingPosition'].quantile(0.25), 
        df['rankingPosition'].quantile(0.75), 
        df['rankingPosition'].quantile(0.9),  
        100000000000
        ]
group_names = [5,4,3,2,1]
df['star'] = pd.cut(df['rankingPosition'], bins, labels=group_names)
df['star_num'] = df['star'].astype(int)

# CHART 1
ax = paris.boundary.plot(color='black')
paris.apply(lambda x: ax.annotate(text=x['l_ar'], xy=x.geometry.centroid.coords[0], ha='center',
            bbox={'facecolor': 'white', 'alpha':1, 'pad': 2, 'edgecolor':'indianred'}), axis=1)
ax.set_axis_off()

# CAFE SAMPLE
df_cafe = df[df['cuisine'].str.contains('Cafe')].sort_values('star')
df_cafe['cuisine'] = df_cafe['cuisine'].str.replace('Cafe|-','')
print(df_cafe.groupby('star').count()['id'])  # number of cafes in each classification

df_cafe_french = df_cafe[df_cafe['cuisine'].str.contains('French')]
print(len(df_cafe_french)/454)  # percentage of french places

# CHART 2
ax = gplt.polyplot(paris, projection=gcrs.AlbersEqualArea())
gplt.pointplot(
                df_cafe, 
                ax=ax, 
                hue="star_num", 
                legend=True,
                cmap="Reds"
                )

print(df_cafe.groupby('l_ar').count()['id'].sort_values())  # number of cafes in each arrondissiment
print(df_cafe[df_cafe['star_num']==5].groupby(['l_ar']).count()['id'].sort_values())  # number of 5-star cafes in each arrondissiment

# CHART 3
paris.index = paris['l_ar']
tmp = df_cafe.groupby('l_ar').sum()['numberOfReviews']
paris_cafe = pd.concat([paris, tmp], axis=1)

ax = gplt.polyplot(paris, projection=gcrs.AlbersEqualArea())
gplt.choropleth(
  paris_cafe,
  hue="numberOfReviews",
  edgecolor="black",
  linewidth=1,
  cmap="Reds",
  legend=True,
  scheme="FisherJenks",
  projection=gcrs.AlbersEqualArea(),
  ax=ax
)

# TYPES OF CAFES 
ncolumns = ['id','star_num','numberOfReviews']
table = df_cafe.groupby(['cuisine']).agg({'id':'count','star_num':'mean','numberOfReviews':'sum'})
table['cuisine_'] = table.index
table['type'] = 'Other'
table['type'] = table['type'].where(table['id']<10,table['cuisine_'])
table = table.groupby(['type']).agg({'id':'sum','star_num':'mean','numberOfReviews':'sum'})
table = table[ncolumns].round(1).sort_values('id', ascending=False)
table["id%"] = round(100*table["id"]/table["id"].sum(),1)
table.columns = ['Total', 'Avg Stars', 'Total Reviews', '%Total']
print(table[['Total', '%Total', 'Avg Stars', 'Total Reviews']])

# THE PERFECT CAFE PLACE
columns = [
    'name',
    'rankingPosition',
    'numberOfReviews',
    'l_ar',
    'star',
]
table = df_cafe_french[columns].set_index('l_ar')
print(table[table['numberOfReviews']>100].sort_values('rankingPosition').head(5))
print(table[table['numberOfReviews']>1000].sort_values('rankingPosition').head(5))