Решение проблемы науки о данных - визуальный подход

Для учеников с визуальным восприятием

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

Главный вопрос, на который я пытался здесь ответить, был: «Какое наиболее идеальное место для магазина розничной торговли в городе?» Я сделал несколько предположений, чтобы дать этому вопросу некоторый контекст, но на самом деле это недалеко от реального исследования, проведенного для клиента.

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

Данные

Как я уже упоминал, у нас есть множество точек данных GPS по всему городу. Ее снимок

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

Ладно, хватит скучных вещей. Ниже показано, как сделать красивую тепловую карту наших данных GPS. Тада…

График

Код

# Add a column with ones, then calculate sum and generate the heat
sliceDF[‘count’] = 1 
# create map of Calgary using latitude and longitude values
base_heatmap = folium.Map(location=[calgLat, calgLng], zoom_start=10)
# Just adding a marker for fun
folium.Marker((lat,lng), popup=”label”).add_to(base_heatmap)
HeatMap(data=sliceDF[[‘Lat’, ‘Lng’, ‘count’]].groupby([‘Lat’, ‘Lng’]).sum().reset_index().values.tolist(), radius=8, max_zoom=4).add_to(base_heatmap)
# If you want to save the map 
base_heatmap.save(outfile= “truckheatMap.html”)
# To show the map in jupyter
base_heatmap

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

График

Код

# Creating list of hours that we need to slice by to generate the time variant map
df_hour_list = []
for hour in sliceDF.Hour.sort_values().unique():
    df_hour_list.append(sliceDF.loc[sliceDF.Hour == hour, ['Lat', 'Lng', 'count']].groupby(['Lat', 'Lng']).sum().reset_index().values.tolist())
base_heattimemap = folium.Map(location=[latitude, longitude], zoom_start=11)
HeatMapWithTime(df_hour_list, radius=8, gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}, min_opacity=0.8, max_opacity=1, use_local_extrema=True).add_to(base_heattimemap)

base_heattimemap

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

График

Код

# Used a def so that if you wish to add interactivity you can do that easily later on.
def plot(min_hour,max_hour,n):
    #boundaries of the main rectangle
    upper_right = [51.1741,-113.8925]
    lower_left = [50.8672,-114.2715]
    
    # Creating a grid of nxn from the given cordinate corners     
    grid = get_geojson_grid(upper_right, lower_left , n)
    # Holds number of points that fall in each cell & time window if provided
    counts_array = []
    
    # Adding the total number of visits to each cell
    for box in grid:
        # get the corners for each cell
        upper_right = box["properties"]["upper_right"]
        lower_left = box["properties"]["lower_left"]
# check to make sure it's in the box and between the time window if time window is given 
        mask = ((sliceDF.Lat <= upper_right[1]) & (sliceDF.Lat >= lower_left[1]) &
            (sliceDF.Lng <= upper_right[0]) & (sliceDF.Lng >= lower_left[0]) &
            (sliceDF.Hour >= min_hour) & (sliceDF.Hour <= max_hour))
# Number of points that fall in the cell and meet the condition 
        counts_array.append(len(sliceDF[mask]))
# creating a base map 
    m = folium.Map(zoom_start = 10, location=[latitude, longitude])
# Add GeoJson to map
    for i, geo_json in enumerate(grid):
        relativeCount = counts_array[i]*100/4345
        color = plt.cm.YlGn(relativeCount)
        color = mpl.colors.to_hex(color)
        gj = folium.GeoJson(geo_json,
                style_function=lambda feature, color=color: {
                    'fillColor': color,
                    'color':"gray",
                    'weight': 0.5,
                    'dashArray': '6,6',
                    'fillOpacity': 0.8,
                })
        m.add_child(gj)
        
    colormap = branca.colormap.linear.YlGn_09.scale(0, 1)
    colormap = colormap.to_step(index=[0, 0.3, 0.6, 0.8 , 1])
    colormap.caption = 'Relative density of fleet activity per cell'
    colormap.add_to(m)
return m
# limiting time window for our data to 8 am - 5 pm and also grid is 20 x 20 
plot(8,17,20)

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

Я довольно много работал с API Google, а позже познакомился с Foursquare, когда начал копаться в науке о данных и может быть потрясающим. Так что тем из вас, кто не знаком с Foursquare, я настоятельно рекомендую его попробовать. Оно того стоит.

Данные сообщества были показаны выше. Пока мы игнорируем ярлыки, которые использовал автор, и предполагаем, что их у нас нет. Наша цель - самостоятельно объединить эти районы и найти подходящую площадь для нашего розничного магазина (склада). Для этого мы используем Foursquare explore API, но не стесняйтесь проверять список всех их API, которые могут пригодиться в ваших проектах.

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

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

Чтобы получить список распространенных мест для кварталов с помощью API Foursquare, выполните следующие действия.

# Using Foursquare's explore API get 10 most common venues around # the latitude, longitude provided within 500 m radius. 
# You'll get the CLIENT_ID, CLIENT_SECRET and VERSION after signing up for Foursquare.(Pay attention to API call limits.)
url = "https://api.foursquare.com/v2/venues/explore?client_id={}&client_secret={}&v={}&ll={},{}&radius=500&limit=10".format(CLIENT_ID,CLIENT_SECRET,VERSION,neigh
borhood_lat,neighborhood_lng)
# results come back in format of JSON 
results = requests.get(url).json()
results

Мы можем распространить это на все окрестности. В приведенном ниже фрейме данных показаны несколько строк результатов.

Одно горячее кодирование

Почему? Потому что у нас есть строки в качестве меток для каждого района, и нам нужен способ их оцифровки, чтобы мы могли использовать их в нашем алгоритме классификации. «Одно горячее кодирование» в основном анализирует ваши метки и присваивает им фиктивные значения, а также создает новые столбцы для каждой метки и использует 1 или 0 для определения погоды в той строке таблицы, которая имеет эту функцию или нет. Так, например, в Spruce Cliff есть кафе, но может не быть тренажерного зала и так далее. В приведенном ниже фрагменте показано, как «быстро кодировать» ваши результаты:

# one hot encoding
calgary_onehot = pd.get_dummies(calgary_venues[['Venue Category']], prefix="", prefix_sep="")
# add neighborhood column back to dataframe
calgary_onehot['Neighbourhood'] = calgary_venues['Neighborhood']
# move neighborhood column to the first column
fixed_columns = [calgary_onehot.columns[-1]] + list(calgary_onehot.columns[:-1])
calgary_onehot = calgary_onehot[fixed_columns]
print("calgary_onehot shape is " , calgary_onehot.shape)
calgary_onehot.head()

Итоговая таблица выглядит примерно так:

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

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

num_top_venues = 10
indicators = ['st', 'nd', 'rd']
def return_most_common_venues(row, num_top_venues):
    row_categories = row.iloc[1:]
    row_categories_sorted = row_categories.sort_values(ascending=False)
    return row_categories_sorted.index.values[0:num_top_venues]
# create columns according to number of top venues
columns = ['Neighborhood']
for ind in np.arange(num_top_venues):
    try:
        columns.append('{}{} Most Common Venue'.format(ind+1, indicators[ind]))
    except:
        columns.append('{}th Most Common Venue'.format(ind+1))
# create a new dataframe
neighborhoods_venues_sorted = pd.DataFrame(columns=columns)
neighborhoods_venues_sorted['Neighborhood'] = calgary_grouped['Neighbourhood']
neighborhoods_venues_sorted.rename(columns={'Neighborhood':"NAME"},inplace=True)
for ind in np.arange(calgary_grouped.shape[0]):
    neighborhoods_venues_sorted.iloc[ind, 1:] = return_most_common_venues(calgary_grouped.iloc[ind, :], num_top_venues)
neighborhoods_venues_sorted.head()

и результат будет примерно таким:

Кластеризация окрестностей

Теперь мы находимся на этапе кластеризации наших окрестностей на основе одного кадра данных с горячим кодированием, который у нас есть. В этом случае я использовал kmeans-clustering из пакета Sklearn и, чтобы иметь возможность сравнивать результаты позже с исходными метками кластера в данных нашего сообщества, я решил использовать n = 4 в качестве количества кластеров.

# set number of clusters
kclusters = 4
calgary_grouped_clustering = calgary_grouped.drop('Neighbourhood', 1)
# run k-means clustering
kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(calgary_grouped_clustering)
# check cluster labels generated for each row in the dataframe
neighborhoods_venues_sorted['labels'] = kmeans.labels_
neighborhoods_venues_sorted.head()

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

График:

Код для этого:

calgary_merged['labels'] = calgary_merged['labels'].astype(int)
map_clusters = folium.Map(location=[latitude, longitude], zoom_start=11)
# set color scheme for the clusters
x = np.arange(kclusters)
ys = [i + x + (i*x)**2 for i in range(kclusters)]
colors_array = cm.rainbow(np.linspace(0, 1, len(ys)))
rainbow = [colors.rgb2hex(i) for i in colors_array]
for cluster in range(0,kclusters): 
    group = folium.FeatureGroup(name='<span style=\\"color: {0};\\">{1}</span>'.format(rainbow[cluster-1],cluster))
    for lat, lon, poi, label in zip(calgary_merged['latitude'], calgary_merged['longitude'], calgary_merged['CLASS_CODE'], calgary_merged['labels']):
        if int(label) == cluster: 
            label = folium.Popup('ORIG. '+ str(poi) + 'Cluster ' + str(cluster), parse_html=True)
            folium.CircleMarker(
                (lat, lon),
                radius=5,
                popup=label,
                color=rainbow[cluster-1],
                fill=True,
                fill_color=rainbow[cluster-1],
                fill_opacity=0.7).add_to(group)
    group.add_to(map_clusters)
folium.map.LayerControl('topright', collapsed=False).add_to(map_clusters)
map_clusters.save(outfile= "map_clusters.html")
map_clusters

Сравнение кластеров с исходными этикетками

Ладно, все в порядке, ну и что? Весь смысл этого исследования состоял в том, чтобы сравнить кластеры с этикетками и попытаться определить подходящее место рядом с центром на минимальном расстоянии для розничного магазина или, скорее, склада.

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

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

Заключение

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

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

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



Геопространственность
(геопространственный Python с открытым исходным кодом)« Что это?
Медианный центр, также известный как центр минимального расстояния, - это… glenbambrick.com »