Пошаговый проект исследовательского анализа данных: от описания бизнес-проблемы до решения и реализации (с кодом). Использование Foursquare API, Beautiful soup, Requests, Pandas и Folium.

Введение и резюме

Я создал этот проект, чтобы завершить Capstone Project для получения Сертификата IBM Professional Data Science Certificate. Я придумал эту бизнес-идею, потому что она находит отклик у меня как пропагандиста здорового образа жизни. Поэтому я хотел бы поделиться с вами поэтапно.

Статья состоит из следующих глав:

  1. Бизнес-задача (Представляем бизнес-идею и мой подход к ее решению);
  2. Анализ данных (описание соответствующих данных и процесса их получения);
  3. Результаты и выводы (выводы на основе результатов и объем будущей работы).

В части анализа данных я создаю этот проект, выполнив следующие действия:

  • получение данных из открытых источников в Интернете (путем парсинга);
  • создание и предварительная обработка (очистка) наборов данных;
  • объединение различных наборов данных и визуальное изучение возможных ответов на проблему;
  • обрисовывая возможные будущие шаги для еще более глубокого изучения этой темы.

Далее сформулируем бизнес-задачу.

Часть 1. Бизнес-проблема

Здесь я хотел бы выделить свою мотивацию, рассуждения и открытые вопросы для этого проекта. А именно, задав себе эти три вопроса: Почему я решил это сделать; Что я собираюсь сделать и Как я планирую с этим справиться.

1.1. Идея (Почему?)

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

Основная причина может заключаться в увеличении знаний о питании и здоровье и / или увеличении доступности чистых источников пищи. В обоих случаях я полностью поддерживаю это движение. Растет количество людей, которым «здоровье превыше всего», как и количество магазинов экологически чистых продуктов.

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

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

1.2. Реализация (Что?)

В настоящее время я живу недалеко от Амстердама, так что это хорошее место для начала.

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

Амстердам даже называют культурной столицей Нидерландов (примечание, административная и королевская столица Нидерландов - Гаага).

Звучит как отличное место для открытия магазина экологически чистых продуктов, не так ли?

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

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

Как бизнес, мы хотим приносить пользу и поддерживать здоровье наших клиентов, но мы также хотим получать взамен прибыль.

Это проблема науки о данных! Как специалист по данным, я собираюсь над этим поработать. Итак, давайте сначала подумаем, как с этим справиться ...

1.3. Деловые вопросы (как?)

В этом проекте мы ответим на следующие вопросы:

  • Сколько там потенциальных клиентов? Кто они и где живут?
  • Сколько магазинов органических продуктов уже существует? Стоит ли открывать еще один магазин?

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

1.4. Предположения

Давайте будем простыми.

  • Всем нужно есть. Так что пол и возраст не должны играть сколько-нибудь значимой роли.
  • Домохозяйства (размер семьи) также не должны играть слишком большую роль. Каждый может вести здоровый образ жизни. Дети обычно следуют примеру своих родителей.
  • Доход не имеет значения, потому что мы нацелены на людей, живущих в Нидерландах. Уровень жизни здесь довольно высок, поэтому я полагаю, что большинство людей могут себе позволить эти более здоровые варианты.
  • Самым важным фактором в нашем анализе будет количество потенциальных клиентов (плотность населения). В некотором смысле я предполагаю, что чем выше плотность населения вокруг магазина, тем выше наши шансы на выживание и рост.

Итак, как подойти к этой проблеме с учетом этих предположений?

1.5. Мой подход

Чтобы справиться с этой проблемой, нам нужно будет получить и сравнить 2 набора данных:

  1. Данные переписи * (плотность населения)
  2. Геопространственные данные (плотность конкурентов)

* Определение: перепись - это процедура систематического подсчета, сбора и регистрации информации о членах данного населения. (из Википедии)

Хорошо. Итак, какие инструменты нам нужны?

1.6. Инструменты

Вот инструменты, которые я буду использовать в этом проекте:

  • Pandas. Если вы работаете с наборами данных, эта библиотека просто необходима. Он имеет множество функций для импорта, обработки и анализа данных. ("официальная страница")
  • Beautiful Soup. Этот пакет Python используется для анализа документов HTML и XML. В основном это полезно для парсинга веб-страниц. ("официальная страница")
  • NumPy. Если вы работаете с массивами, словарями, функциями, типами данных и изображениями, вам необходимо знать NumPy. ("официальная страница")
  • GeoPy. Этот пакет Python используется для определения координат адресов, городов, стран и ориентиров по всему миру с помощью сторонних геокодеров и других источников данных. ("официальная страница")
  • Tqdm (необязательно). Эта библиотека используется для того, чтобы циклы отображали интеллектуальный индикатор / полоску прогресса. Это необязательно, но мне было приятно отслеживать прогресс веб-парсинга. ("документация")
  • Folium. Библиотека Python для визуализации карт, которая упрощает визуализацию данных на интерактивной карте Leaflet. ("документация")

1.7. Часть 1 Резюме

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

Теперь мы готовы приступить к кодированию. Давайте воплотим этот проект в жизнь!

Часть 2. Анализ данных.

Я делю эту главу на несколько подразделов.

  • В части 2.1 я собираю в Интернете доступные данные, исследую районы Амстердама и составляю хорошую карту для визуального осмотра и общего понимания этой бизнес-проблемы.
  • В части 2.2 я исследую данные переписи, чтобы получить представление о наиболее густонаселенных районах (потенциально наиболее прибыльных).
  • В Части 2.3 я ищу конкурентов (другие магазины экологически чистых продуктов). Это равносильно изучению заведений данного типа / названия.

Часть 2.1: Подготовка и предварительная визуализация

Согласно статистике, примерно 65 процентов населения обучаются визуально.

Итак, давайте визуализируем проблему. Я хочу увидеть карту Амстердама с его окрестностями.

Что нам нужно?

Для этого нам нужно знать postalcodes (zip) каждого района и соответствующие им значения latitude и longitude.

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

Как это сделать?

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

  1. Найдите в Интернете почтовые индексы с помощью beautiful soup и requests,
  2. Найдите соответствующие координаты (широту и долготу) с помощью geopy,
  3. Объедините все данные в pandas фрейм данных,
  4. Визуализируйте окрестности с folium.

Давайте пройдемся по этим 4 шагам один за другим.

1. Скребок Интернета. Давайте импортируем все библиотеки

# library to handle data in a vectorized manner
import numpy as np 
# library to load dataframe
import pandas as pd
# library to handle requests
import requests
# library to handle advanced web scraping
from bs4 import BeautifulSoup
# library to render map
import folium

Далее нам нужно найти сайт открытых данных с почтовыми индексами Амстердама.

# website with Amsterdam postal codes
page = requests.get('https://postcode.site/noord-holland/amsterdam')
page

Результат должен быть

<Response [200]> означает, что наш запрос был выполнен - ​​мы попали на этот сайт. Для нас это зеленый свет.

Затем мы проверяем веб-страницу, чтобы выяснить, какие части актуальны для нас (это class=”list-group inverse”). Затем мы создаем экземпляр красивого супа и используем его метод find для получения почтовых индексов.

# creating an instance soup of BeautifulSoup class to parse the document
soup = BeautifulSoup(page.content, 'html.parser')
# a nice pretty printout
# print(soup.prettify())
all_postcodes = soup.find(class_="list-group inverse")
# print(all_postcodes.prettify())

Наконец, мы можем перебрать результат, чтобы создать список значений

all_zips_list = all_postcodes.find_all('li')
# creating empty array to write all these zip codes:
all_zips = []
for zip_ in all_zips_list:
#     print(zip_.get_text())
    all_zips.append(zip_.get_text())
print("The list of all Amsterdam zip codes:")
print(all_zips)

Результат должен понравиться

Как видим, это всего лишь 4-значные части почтовых индексов. Нам еще нужно получить вторую часть, это 2 буквы.

После более глубокого изучения текущего веб-сайта мы обнаруживаем, что следующий адрес показывает полный почтовый индекс, основанный на его 4-значной части:

https://postcode.site/noord-holland/amsterdam/number

где number - это 4-значное число соответствующего почтового индекса.

Итак, мы можем перебирать список из 4 цифр, чтобы заполнить почтовые индексы. Для этого я определяю функцию, которая будет делать это автоматически:

def scrape_full_zips(zip_):
    
    print("Working with zip code {}".format(zip_))
    # accessing the relevant page for a specific postcode
    page = requests.get("https://postcode.site/noord-holland/amsterdam/{}".format(zip_))
    soup = BeautifulSoup(page.content, 'html.parser')
    
    # getting the right table
    zipBeautiful Soupdigit = soup.find(class_="table table-bordered table-striped table-pnum-one")
#     print(zipBeautiful Soupdigit.prettify())
    
    # dissecting the table on elements
    table_rows = zipBeautiful Soupdigit.find_all('tr')
    
    # appending each row in the final array
    final_array = []
    
    for tr in table_rows:
        #table data/cell
        td = tr.find_all('td')
        row = list([i.text for i in td])
#         final_array.append(row[0])
        try:
#             print(row[0])
            final_array.append(row[0])
            
            
        except Exception as e:
#             print(e)
            pass
        
    return final_array

И вызовите эту функцию

# creating Pandas data frame
df = pd.DataFrame(columns=["Postal Code"])
df.head()
# creating empty list to append all of the postal codes
My_array = []
# iterating over every set of postal codes
for zip_ in all_zips:s
    # running my function of parsing the postal codes
    My_array.extend(scrape_full_zips(zip_ = zip_))
print(My_array)

Результат выглядит так

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

My_array2 = []
for i, postal_code in enumerate(My_array):
    My_array2.append(postal_code[0:4] + " " + postal_code[4:6])
# appending our data frame with every postal code
df["Postal Code"] = My_array2
# quick look at our data frame
df

Этот результат уже многообещающий

Нам просто нужно немного его очистить, удалить значения NaN, пустые ячейки и т. Д.

# dropping possible nan values
df2 = df.dropna(subset = ["Postal Code"], inplace=False)
# selecting empty line postal codes
index_to_drop = df2[(df2["Postal Code"] == ' ')].index
# deleting these row indexes from data frame
df2.drop(index_to_drop, inplace=True)
print("DataFrame size before dropping {}".format(df.shape[0]))
print("DataFrame size after dropping {}".format(df2.shape[0]))

Мы можем сравнить размер фрейма данных до и после очистки. Как видим, очистка удалила 274 нефункциональных ячейки:

Теперь мы готовы искать значения широты и долготы для каждого почтового индекса.

2. Найдите соответствующие координаты

В следующем блоке кода мы создаем пользовательский агент с возможным расширением задержек между вызовами (чтобы не перегружать веб-сайт), затем выбираем только первые 500 адресов (чтобы ускорить процесс и не перебирать все 19625 почтовых сообщений. коды) и, наконец, получить наши координаты, сохраненные в переменных longitude и latitude соответственно

from geopy.geocoders import Nominatim
geocoder = Nominatim(user_agent = 'Organic_Shop_Project')
# adding 0 second padding between calls
from geopy.extra.rate_limiter import RateLimiter
geocode = RateLimiter(geocoder.geocode, min_delay_seconds = 0.0, return_value_on_exception = None)
latitude = []
longitude = []
# Something cool to check the progress 
from tqdm import tqdm
    
# Let us go over each postal code and get the coordinates:
# for i in tqdm(range(len(df2["Postal Code"]))):
for postal_code in tqdm(df2["Postal Code"][:500]):
    
#     postal_code = df2["Postal Code"][i]
    
#     print("\nWorking on postal code {} ...".format(postal_code))
    
    # initialize the variable to None
    lat_lng_coords = None
    
    attempt = 0
    # loop until we get the coordinates (because sometimes it does not get them at once)
    while(lat_lng_coords is None):
        
        address = "{}, Amsterdam, NL".format(postal_code)
#         print(address)
        # after initiating geocoder
        location = geocode(address)
        # returns location object with longitude, latitude and altitude instances
        try:
            lat_lng_coords = (location.latitude, location.longitude)
        except Exception as e:
#             print(e)
            attempt +=1
        if attempt >= 5:
            lat_lng_coords = (np.nan, np.nan)
            break
        
    latitude.append(lat_lng_coords[0])
    longitude.append(lat_lng_coords[1])

Используя пакет tqdm, я могу отслеживать прогресс следующим образом:

Примечание. Мы выбрали только первые 500 почтовых индексов, чтобы уменьшить размер набора данных и ускорить процесс. В принципе, для нашего анализа этого должно хватить.

3. Объедините все данные в фрейм данных.

Затем мы хотим создать новый фрейм данных только с первыми 500 элементами, которые мы добавим с нашими новыми координатами.

# creating a new data frame to hold only the first 500 elements
df3 = df2.head(500)
df3.head()
df3[['Longitude']] = longitude
df3[['Latitude']] = latitude
# first 10 rows
df3.head(10)

Это приводит к следующей таблице

Небольшая очистка, чтобы избавиться от этих значений NaN, и мы закончили с этой частью:

df3.dropna(subset = ["Postal Code"], how='any', inplace=True)

Мы могли бы сохранить этот фрейм данных в файле с разделителями-запятыми (.csv), чтобы он всегда был у нас под рукой. Потому что повторение всего процесса парсинга веб-страниц и поиска координат - довольно трудоемкий процесс. Просто используйте метод .to_csv() библиотеки padas

# let us save the data frame into a csv file
df3.to_csv("Ruslan_Amsterdam_postcodes.csv", encoding='utf-8', index=False)

4. Визуализируйте окрестности Амстердама.

В случае, если мы начинаем с этого момента, мы можем просто загрузить наш сохраненный предварительно обработанный (очищенный) фрейм данных в переменную df3, используя метод pandas .read_csv() следующим образом:

df3 = pd.read_csv('Ruslan_Amsterdam_postcodes.csv')
df3.head()

Давайте получим координаты города Амстердам для центрирования нашей карты по тому же принципу (пакет geopy)

from geopy.geocoders import Nominatim
geocoder = Nominatim(user_agent = 'Organic_Shop_Project')
# adding 0 second padding between calls
from geopy.extra.rate_limiter import RateLimiter
geocode = RateLimiter(geocoder.geocode, min_delay_seconds = 0.0, return_value_on_exception = None)
geolocator = Nominatim(user_agent="ams_explorer")
location = geolocator.geocode("Amsterdam, NL")
# returns location object with longitude, latitude and altitude instances
latitude_ams = location.latitude
longitude_ams = location.longitude
print('The geograpical coordinate of Amsterdam City are {}, {}.'.format(latitude_ams, longitude_ams))

И теперь мы создаем нашу карту, инициализируя объект листа map_ams вместе с маркерами окрестностей, как показано ниже.

# create map of Amsterdam using latitude and longitude values
map_ams = folium.Map(location=[latitude_ams, longitude_ams], zoom_start=12)
# add markers to map
for lat, lng in zip(df3['Latitude'], df3['Longitude']):
#     print(lat, lng)
    label = '{}, {}'.format(df3['Latitude'], df3['Longitude'])
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=4,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#3186cc',
        fill_opacity=0.7,
        parse_html=False).add_to(map_ams)  
    
map_ams

В результате получилась красивая интерактивная карта Leaflet.

Это настоящая раздача! Выглядит здорово, не правда ли?

Сразу видно, что выбранные кварталы сконцентрированы в сторону Центрального вокзала Амстердама, что нормально для любого города. Это уже дает нам некоторые подсказки, где мы могли бы открыть наш магазин органических продуктов.

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

Нам все еще необходимо изучить следующие вопросы:

  1. А как насчет плотности населения в районе Амстердама?
  2. А как насчет других конкурентов?

Мы начинаем с первого вопроса, и для этого нам нужно получить данные переписи.

Часть 2.2. Изучение данных переписи

2.2.1. Веб-парсинг с открытыми исходными кодами

Нам нужно получить данные. Давайте исследуем этот веб-сайт открытых данных, чтобы найти соответствующую информацию. После загрузки соответствующего набора данных HTML (назовем его Amsterdam.html) мы можем прочитать его во фрейме данных pandas следующим образом:

# Let us download the data in form of the HTML and raed it in the data frame
df_ams = pd.read_html("Amsterdam.html", skiprows=0, header=None)[0]
df_ams

Это покажет огромную довольно грязную таблицу.

Давайте немного очистим это.

После переименования столбцов следующим образом,

df_ams.columns = ["Districts and neighborhoods", "Districts and neighborhoods copy", "Municipality", 
                  "Population", "Population density", "Number of income recipients", 
                  "Average income per income recipient", "Average income per inhabitant",
                 "40% persons with the lowest income", "20% persons with the highest income",
                 "Active 15-75 years", "40% households with the lowest income", 
                 "20% households with the highest income", "low income housholds", "below or around the social minimum", 
                  "Most common zip code", "Coverage percentage"]

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

df_ams[["Districts and neighborhoods"]]

Полный код с полным анализом можно найти в моем репозитории GitHub.

2.2.2. Первая визуализация

После предварительной обработки и сохранения этого набора данных мы можем загрузить и визуализировать его следующим образом:

# import folium
# create map of Amsterdam using latitude and longitude values
map_ams = folium.Map(location=[latitude_ams, longitude_ams], zoom_start=12)
# add markers to map
for lat, lng in zip(df_ams2['Latitude'], df_ams2['Longitude']):
#     print(lat, lng)
    label = '{}, {}'.format(df_ams2['Latitude'], df_ams2['Longitude'])
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=4,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#3186cc',
        fill_opacity=0.7,
        parse_html=False).add_to(map_ams)  
    
map_ams

Результат будет очень похож на то, что мы уже видели.

Если меня спросят: «Итак, Руслан, какой смысл повторять один и тот же процесс?», я отвечу, что ошибаюсь. Мы не просто повторили этот процесс, а продвинулись намного дальше - мы получили и очистили данные переписи, что не то же самое, что просто показать карту Амстердама. Чтобы показать это, давайте посмотрим на плотность населения.

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

2.2.3. Визуализация плотности населения

Итак, давайте объединим районы и покажем их с тепловой картой для получения приятных визуальных эффектов:

# let us craete a general map object centered on Amsterdam location, it will make life easier
# to not create this object every time we want to show a new map:
def generateBaseMap(default_location=[latitude_ams, longitude_ams], default_zoom_start=12):
    base_map = folium.Map(location=default_location, control_scale=True, zoom_start=default_zoom_start)
    return base_map
from folium.plugins import HeatMap
df_copy = df_ams2.copy()
df_copy['count'] = 1
base_map = generateBaseMap()
# add markers to map
for lat, lng in zip(df_ams2['Latitude'], df_ams2['Longitude']):
#     print(lat, lng)
    label = '{}, {}'.format(df_ams2['Latitude'], df_ams2['Longitude'])
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=4,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#7b3b3a',
        fill_opacity=0.7,
        parse_html=False).add_to(base_map)
HeatMap(data=df_copy[['Latitude', 'Longitude', 'count']].groupby(['Latitude', 'Longitude']).sum().reset_index().values.tolist(), radius=15, max_zoom=12).add_to(base_map)
base_map

В результате получится:

Это здорово выглядит! Но давайте взглянем на данные и покажем их на карте немного по-другому:

# create map of Amsterdam using latitude and longitude values
map_ams = folium.Map(location=[latitude_ams, longitude_ams], zoom_start=14)
# add markers to map
for lat, lng, pop in zip(df_ams2['Latitude'], df_ams2['Longitude'], df_ams2['Population density']):
#     print(pop)
#     print(lat, lng)
    label = '{}, {}'.format(df_ams2['Latitude'], df_ams2['Longitude'])
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=float(pop.replace(" ", ""))/1500.,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#3186cc',
        fill_opacity=0.25,
        parse_html=False).add_to(map_ams)  
    
map_ams

Это приводит к следующей карте:

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

2.2.4. Визуализация наиболее прибыльных районов

Выберем только районы с самой высокой плотностью населения, чтобы оставить только потенциально самые прибыльные места.

# Once more, let us convert the string into floats for the Population Density feature
pop_dens_list = list(df_ams2['Population density'])
for indx, pop_dens in enumerate(pop_dens_list):
    pop_dens_list[indx] = float(pop_dens.replace(" ", ""))
df_ams2['Population density float'] = pop_dens_list
df_ams2['Population density float']
# Visualization:
# print(df_ams2.keys() )
df_ams3 = df_ams2[['Districts and neighborhoods', 'Population density float']]
df_ams3.sort_values(["Population density float"], axis=0, ascending=False, inplace=True)
# df_ams3.head()
ax = df_ams3.plot(kind='barh', figsize=(14,10))
ax.set_title('Population density per neighborhood in Amsterdam')
ax.set_xlabel('Population Density')
ax.set_ylabel('Districts and neighborhoods')
ax.invert_yaxis()
plt.setp(ax.get_yticklabels()[::2], visible=False)
plt.setp(ax.get_yticklabels()[::3], visible=False)
# use axvline to mark the average population density
mean = df_ams3["Population density float"].mean()
ax.axvline(mean, color='#2B9B2A') #Green

В результате мы получаем гистограмму плотности населения на окрестности Амстердама:

Что мы можем узнать из рисунка выше?

  1. По сравнению со средней плотностью населения (зеленая сплошная линия) на район, есть несколько районов со значительно более высокой (и значительно более низкой) плотностью населения. Это неудивительно, поскольку в окрестностях Амстердама есть множество небольших городов.
  2. Чтобы продолжить наш анализ, мы хотим выбрать наиболее густонаселенные районы. Я бы выбрал 1/3 от максимального значения, чтобы максимизировать потенциальный доход от наших клиентов.

2.2.5. Выбор наиболее густонаселенных районов

max_density = int(df_ams3["Population density float"].max())
print("Maximum population density of Amsterdam's neighborhoods is: {}".format(max_density))
df_ams3 = df_ams3[df_ams3["Population density float"] > max_density/3.]
df_ams3.head()

То есть:

Наконец, объединив эти районы с их широтой и долготой, мы можем визуализировать эти районы следующим образом:

df_merge = pd.merge(df_ams3, df_ams2, on=['Districts and neighborhoods'])
df_merge.head()
from folium.plugins import HeatMap
df_copy2 = df_merge.copy()
df_copy2['count'] = 1
base_map = generateBaseMap()
# add markers to map
for lat, lng, pop in zip(df_merge['Latitude'], df_merge['Longitude'], df_merge['Population density float_x']):
#     print(lat, lng)
    label = '{}, {}'.format(df_merge['Latitude'], df_merge['Longitude'])
    label = folium.Popup(label, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=pop/1500.,
        popup=label,
        color='blue',
        fill=True,
        fill_color='#7b3b3a',
        fill_opacity=0.7,
        parse_html=False).add_to(base_map)
base_map

В результате мы получаем нашу финальную карту-листок с плотностью населения:

где размер круга представляет плотность населения. Более крупный круг = более высокая популяция.

Следующие шаги

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

Часть 2.3 Поиск конкурентов

Это эквивалентно изучению близлежащих заведений с заданным названием (например, Organic).

2.3.1. Выбор первого района для исследования

Здесь я буду использовать API Foursquare для исследования мест (кварталов).

Нам нужно указать координаты для запроса - мы можем начать с первой окрестности в нашем списке:

# resetting index
df_merge = df_merge.reset_index(inplace=False)
df_merge.head()
# selecting Neighbourhood based on index:
df_merge.loc[0, 'Districts and neighborhoods']
# neighborhood latitude value
neighborhood_latitude = df_merge.loc[0, 'Latitude'] 
# neighborhood longitude value
neighborhood_longitude = df_merge.loc[0, 'Longitude'] 
# neighborhood name
neighborhood_name = df_merge.loc[0, 'Districts and neighborhoods']
print("Latitude and longitude values of '{}' are {}, {}.".format(neighborhood_name, 
                                                               neighborhood_latitude, 
                                                               neighborhood_longitude))

В результате получаем:

2.3.2. Изучение мест вокруг первого квартала

Учитывая радиус поиска по заданной координате (долгота и широта), ограничение на количество запросов и поисковый запрос, то есть в нашем случае будет Organic

search_radius = 1000 # meters, i.e., 1km
LIMIT = 100
search_query = "Organic"
neighborhood_latitude = # coordinate of our first neighborhood
neighborhood_longitude = # coordinate of our first neighborhood

мы можем вытащить запрос с указанными учетными данными (я не показываю свои учетные данные из соображений конфиденциальности, но URL-адрес следующий):

url = 'https://api.foursquare.com/v2/venues/search?client_id={}&client_secret={}&ll={},{}&oauth_token={}&v={}&query={}&radius={}&limit={}'.format(CLIENT_ID, CLIENT_SECRET, neighborhood_latitude, neighborhood_longitude, ACCESS_TOKEN, VERSION, search_query, search_radius, LIMIT)

Примечание: это определение URL-ссылки для изучения venues через Foursquare API. Чтобы получить результат запроса с этой веб-страницы, мы просто используем метод .get() из requests библиотеки:

results = requests.get(url).json()

Теперь у нас есть results в формате JSON *.

* JSON (нотация объектов JavaScript) - это открытый стандартный формат файла и формат обмена данными, в котором для хранения и передачи объектов данных используется читаемый человеком текст. Вот ссылка на Википедию, чтобы узнать о ней больше.

Есть много разных площадок. Давайте определим функцию, которая извлекает категорию места проведения из requests результата:

def get_category_type(row):
    try:
        categories_list = row['categories']
    except:
        categories_list = row['venue.categories']
        
    if len(categories_list) == 0:
        return None
    else:
        return categories_list[0]['name']

поэтому мы можем вызвать эту функцию, чтобы создать фрейм данных из этого файла JSON:

# tranform JSON file into a pandas dataframe
from pandas.io.json import json_normalize
venues = results['response']['venues']
    
nearby_venues = json_normalize(venues) # flatten JSON
# filter columns
# filtered_columns = ['venue.name', 'venue.categories', 'venue.location.lat', 'venue.location.lng']
filtered_columns = ['name', 'categories', 'location.lat', 'location.lng']
nearby_venues =nearby_venues.loc[:, filtered_columns]
# filter the category for each row
nearby_venues['venue.categories'] = nearby_venues.apply(get_category_type, axis=1)
# clean columns
nearby_venues.columns = [col.split(".")[-1] for col in nearby_venues.columns]
nearby_venues.head()

Отлично! Это уже существующие магазины органических продуктов рядом с нашим первым районом.

Давайте повторим эту процедуру автоматически для каждого района в нашем наборе данных Амстердама.

2.3.3. Изучение мест в каждом районе

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

def getNearbyVenues(names, latitudes, longitudes, radius=1000):
    
    venues_list=[]
    for name, lat, lng in zip(names, latitudes, longitudes):
        print(name)
            
        # create the API request URL
        url = 'https://api.foursquare.com/v2/venues/search?client_id={}&client_secret={}&ll={},{}&oauth_token={}&v={}&query={}&radius={}&limit={}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            lat, 
            lng, 
            ACCESS_TOKEN, 
            VERSION, 
            search_query, 
            radius, 
            LIMIT)
            
        # make the GET request
        results = requests.get(url).json()["response"]['venues']
        
        # return only relevant information for each nearby venue
        venues_list.append([(
            name, 
            lat, 
            lng, 
            v['name'], 
            v['location']['lat'], 
            v['location']['lng'],  
            v['categories'][0]['name']) for v in results])
nearby_venues = pd.DataFrame([item for venue_list in venues_list for item in venue_list])
    nearby_venues.columns = ['Neighborhood', 
                  'Neighborhood Latitude', 
                  'Neighborhood Longitude', 
                  'Venue', 
                  'Venue Latitude', 
                  'Venue Longitude', 
                  'Venue Category']
    
    return(nearby_venues)

И, конечно же, чтобы этот ребенок работал, мы просто вызываем его с указанными параметрами:

amsterdam_venues = getNearbyVenues(names=df_merge['Districts and neighborhoods'],
                                   latitudes=df_merge['Latitude'],
                                   longitudes=df_merge['Longitude']
                                  )

В логгере мы можем отслеживать процесс со следующим выводом:

Давайте исследуем полученные данные:

print(amsterdam_venues.shape)
# amsterdam_venues.head()
amsterdam_venues['Venue']

При просмотре площадок сразу же видны дубликаты - ничего необычного. Объяснение этому довольно простое.

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

Есть как минимум два решения этой проблемы:

  1. Избегайте итераций по районам вообще и ищите все возможные места в Амстердаме в целом.
  2. Удалите дубликаты.

Позвольте мне применить первое решение, потому что я хотел бы еще немного поэкспериментировать с Foursquare API. Я хочу расширить свой поиск или увеличить количество рынков и других возможных органических мест.

2.3.4. Расширение поиска Foursquare API

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

Таким образом, мы избежим дублирования, но все равно получим полный requests результат, потому что мы ищем все подходящие места в Амстердаме.

# Let me define a new function to search for a largen variety of venues
# over a larger area
# but for the fixed coordinates (center of Amsterdam)
def getNearbyVenues2(search_query, names, latitudes, longitudes, radius=10000, LIMIT = 500):
    
#     name, lat, lng = names, latitudes, longitudes
    name, lat, lng = "Amsterdam", latitude_ams, longitude_ams
    
    venues_list=[]
    for query in zip(search_query):
        print("Searching now for {}".format(query))
            
        # create the API request URL
        url = 'https://api.foursquare.com/v2/venues/search?client_id={}&client_secret={}&ll={},{}&oauth_token={}&v={}&query={}&radius={}&limit={}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            lat, 
            lng, 
            ACCESS_TOKEN, 
            VERSION, 
            query, 
            radius, 
            LIMIT)
            
        # make the GET request
        results = requests.get(url).json()["response"]['venues']
        
        # return only relevant information for each nearby venue
        venues_list.append([(
            name, 
            lat, 
            lng, 
            v['name'], 
            v['location']['lat'], 
            v['location']['lng'],  
            v['categories'][0]['name']) for v in results])
nearby_venues = pd.DataFrame([item for venue_list in venues_list for item in venue_list])
    nearby_venues.columns = ['Neighborhood', 
                  'Neighborhood Latitude', 
                  'Neighborhood Longitude', 
                  'Venue', 
                  'Venue Latitude', 
                  'Venue Longitude', 
                  'Venue Category']
        
    return(nearby_venues)

Вот список новых ключевых слов или слов запроса:

query_list = ["Organic Grocery", "Organic", "Marqt"]
amsterdam_venues2 = getNearbyVenues2(search_query = query_list,
                                    names=df_merge['Districts and neighborhoods'],
                                    latitudes=df_merge['Latitude'],
                                    longitudes=df_merge['Longitude'],
                                    LIMIT = 500
                                  )

Мы можем посмотреть на наш фрейм данных:

np.size(amsterdam_venues2[‘Venue Longitude’].unique())

Результат - 24. Это означает 24 уникальных заведения по всему Амстердаму. давайте посмотрим поближе, чтобы перепроверить себя:

amsterdam_venues2.head()

Это именно то, что мы искали! Полный список уникальных конкурентов в Амстердаме с указанием их позиций.

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

Часть 3. Результаты и выводы.

Здесь я провожу окончательный анализ, исследуя карту Амстердама с соответствующими районами и конкурентами (органическими магазинами).

3.1. Визуализация органических магазинов на карте

# Initializing the map object
base_map = generateBaseMap()
# # add markers to map
# for lat, lng, pop, hood in zip(df_merge['Latitude'], df_merge['Longitude'], df_merge['Population density float_x'], df_merge['Districts and neighborhoods']):
HeatMap(data=df_copy2[['Latitude', 'Longitude', 'count']].groupby(['Latitude', 'Longitude']).sum().reset_index().values.tolist(), radius=15, max_zoom=12).add_to(base_map)
# adding Organic Shops on our map:
# add pop-up text to each marker on the map
latitudes = list(amsterdam_venues2['Venue Latitude'])
longitudes = list(amsterdam_venues2['Venue Longitude'])
labels = list(amsterdam_venues2['Venue'])
print(len(latitudes), len(longitudes), len(labels))
for lat, lng, label in zip(latitudes, longitudes, labels):
#     print(lat, lng)
    folium.Marker([lat, lng], icon=folium.Icon(color='blue', icon='glyphicon-star'), popup=label).add_to(base_map)
base_map

Что мы можем сделать из этой окончательной карты?

  • Похоже, что в центральном районе (с наибольшей плотностью кварталов) нет ни одного магазина экологически чистых продуктов → возможная причина - высокая стоимость аренды, потому что он находится недалеко от Центрального вокзала.
  • Визуально есть некоторые потенциальные области с меньшим количеством конкурентов, но более высоким доступом к районам. Но за этим могут быть какие-то скрытые причины.
  • Чтобы сделать окончательный вывод, необходимы дополнительные данные и углубленный анализ. Возможно, это будет сделано в будущих работах, и это станет отличным продолжением этого проекта. Здесь я завершу анализ, используя имеющиеся у меня данные.

3.2. Изучение менее конкурентоспособных мест

Давайте теперь ближе познакомимся с нашими конкурентами. Для этого я хотел бы нарисовать круг длиной 1 км вокруг каждого магазина органических продуктов:

# First of all, let me add a certain area around each Organic Shop to see how many neighborhoods are included there
base_map = generateBaseMap(default_zoom_start = 13)
HeatMap(data=df_copy2[['Latitude', 'Longitude', 'count']].groupby(['Latitude', 'Longitude']).sum().reset_index().values.tolist(), radius=15, max_zoom=12).add_to(base_map)
# add pop-up text to each marker on the map
latitudes = list(amsterdam_venues2['Venue Latitude'])
longitudes = list(amsterdam_venues2['Venue Longitude'])
labels = list(amsterdam_venues2['Venue'])
print(len(latitudes), len(longitudes), len(labels))
for lat, lng, label in zip(latitudes, longitudes, labels):
#     print(lat, lng)
    folium.Marker([lat, lng], icon=folium.Icon(color='blue', icon='glyphicon-star'), popup=label).add_to(base_map)
# add a grey circle to represent the search radius
    folium.Circle([lat, lng], radius=1000, color='#004B7F', opacity=0.3, fill = False, fill_opacity=0.3).add_to(base_map)
base_map

Это уже что-то!

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

# First of all, let me add a certain area around each Organic Shop to see how many neighborhoods are included there
base_map = generateBaseMap(default_zoom_start = 12)
# add markers to map
for lat, lng, pop, hood in zip(df_merge['Latitude'], df_merge['Longitude'], df_merge['Population density float_x'], df_merge['Districts and neighborhoods']):
#     print(lat, lng)
    label = folium.Popup(hood, parse_html=True)
    folium.CircleMarker(
        [lat, lng],
        radius=pop/1500.,
        popup=label,
        color='red',
        fill=True,
        fill_color='#7b3b3a',
        fill_opacity=0.3,
        parse_html=False).add_to(base_map)
# HeatMap(data=df_copy2[['Latitude', 'Longitude', 'count']].groupby(['Latitude', 'Longitude']).sum().reset_index().values.tolist(), radius=15, max_zoom=12).add_to(base_map)
# add pop-up text to each marker on the map
latitudes = list(amsterdam_venues2['Venue Latitude'])
longitudes = list(amsterdam_venues2['Venue Longitude'])
labels = list(amsterdam_venues2['Venue'])
print(len(latitudes), len(longitudes), len(labels))
for lat, lng, label in zip(latitudes, longitudes, labels):
#     print(lat, lng)
    folium.Marker([lat, lng], icon=folium.Icon(color='blue', icon='glyphicon-star'), popup=label).add_to(base_map)
# add a grey circle to represent the search radius
    folium.Circle([lat, lng], radius=1000, color='#004B7F', opacity=0.9, fill = True, fill_opacity=0.9).add_to(base_map)
base_map

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

Выводы:

Основываясь на приведенном выше рисунке, я могу предположить, что лучшие места для открытия нового органического магазина - это районы:

  • Осдорп-Восток, Осдорп Центральный Север, Дон Боско (западная дальняя часть Амстердама)
  • Коленкит, Северный Коленкитбуурт, Юг Коленкитбуурт (западная близкая часть Амстердама)
  • Вокруг Валкенбурга (возле Центрального вокзала)

Как и все остальное, открытие нового магазина сопряжено с определенными рисками. Однако этот предварительный вывод подтверждается общедоступными данными. Итак, в качестве предварительного * вывода я бы порекомендовал открыть новый магазин органических продуктов в одном из вышеупомянутых мест.

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

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

На этом мы завершаем наш анализ.

Спасибо, что прочитали мой проект Data Science. Надеюсь, вы узнали что-то новое и получили от этого удовольствие, как и я, работая над этим и делясь своими идеями и опытом.

Вот мой репозиторий GitHub, который включает полный код, который я объяснил выше.

Если хотите подключиться, вот мой LinkedIn.

Хорошего дня. Оставайтесь в целости и сохранности.

использованная литература

Вот список ресурсов, которые я использовал для завершения этого проекта:

  1. Официальная страница Pandas
  2. Официальная страница ofBeautiful Soup
  3. Официальная страница ofNumPy
  4. Официальная страница GeoPy
  5. Документация Tqdm
  6. Документация Folium
  7. Сайт Open Data с почтовыми индексами Амстердама
  8. Сайт Open Data с данными переписи Амстердама

P.S .: Если вам нравится непрерывное чтение на этой прекрасной платформе Medium.com, подумайте о поддержке авторов этого сообщества, подписавшись на членство ЗДЕСЬ. Это стоит всего 5 долларов в месяц и поддерживает всех авторов.