День святого Валентина — одна из моих любимых дат в году. 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))