Это вторая из четырех статей, направленных на решение проблемы выявления вспышек заболеваний путем извлечения заголовков новостей из популярных источников новостей.
Эта статья призвана уточнить заголовки, извлеченные из предыдущего анализа данных (в первой статье). После завершения этого процесса он присваивает заголовок городу, штату, округу или стране в зависимости от присутствующих ключевых слов; если нет ключевых слов, заголовок удаляется. С его помощью каждому заголовку присваиваются широта и долгота; кроме того, после нахождения оптимального количества кластеров реализуется алгоритм кластеризации k-средних, который группирует заголовки в соответствии с их координатами широты и долготы. Приведенные ниже шаги предоставляют исчерпывающий обзор процесса, подробно описанного в этом параграфе.
Шаг 1. Чтение заголовков
Файл «headline_all ({date}). Txt» считывается и добавляется к массиву заголовков.
file1 = open('headline_all({date}).txt', 'r') headline_lines = file1.readlines() headlines = [] for line in headline_lines: headlines.append(line.strip()) headlines = list(set(headlines)) print(len(headlines))
Шаг 2. Уточнение заголовков
Название новостного канала удаляется из конца заголовка, чтобы данные не искажались (например, «Los Angeles Times»).
for i in range(len(headlines)): headline = str(headlines[i]) size = len(headline)-2 if (size <= 0): continue while (size != 0): if (headline[size-1:size+2] == " | " or headline[size-1:size+2] == ' - ' or headline[size-1:size+2] == ' – '): break size-=1 if (size != 0): headlines[i] = headline[:size-1]
Шаг 3. Импорт библиотек
Каждая из импортированных библиотек вносит значительный вклад в код.
import pandas as pd import geonamescache import numpy as np import re import unidecode from sklearn.cluster import KMeans from mpl_toolkits.basemap import Basemap import matplotlib.pyplot as plt from sklearn.cluster import DBSCAN from math import radians, cos, sin, sqrt, asin import collections
Шаг 4. Получение названий страны, округа и штата
Сначала с помощью библиотеки geonamescache извлекаются названия стран. Создается словарь, в котором хранится название страны и соответствующий счетчик; вначале он инициализируется нулем, так как заголовки не анализируются. Последующий процесс был выполнен с названиями округов и штатов; для названий штатов также был сохранен штат с соответствующим кодом (например, Калифорния и Калифорния были сохранены).
gnc = geonamescache.GeonamesCache() country_names = [k for k in gnc.get_countries_by_names()] # Country country_counter = {} for index in country_names: country_counter[index] = 0 # County county_names = {} county_counter = {} for index in gnc.get_us_counties(): if (not index['name'] in county_names): county_names[str(index['name'])] = str(index['state']) county_counter[str(index['name'])] = 0 county_names_sorted = list(county_names.keys()) county_names_sorted.sort() # State state_names = [] state_keys = {} state_counter = {} state_repository = gnc.get_us_states() for index in list(state_repository.keys()): state_names.append(state_repository[index]['name']) state_keys[state_repository[index]['code']] = state_repository[index]['name'] state_counter[state_repository[index]['name']] = 0
Шаг 5. Проверка заголовков городов, штатов, округов и стран
Сначала инициализируется словарь, содержащий соответствующую страну, город, штат и округ для каждого заголовка, если таковой имеется. Заголовки нормализованы (акценты и специальные символы удалены), чтобы устранить любые несоответствия в разных заголовках. Сначала определяются возможные города. Заголовок разбит на массив слов. Подстроки из заголовка, которые определены как город с помощью библиотеки geonamescache, добавляются в массив Poss_cities. В качестве города, который будет добавлен в словарь, будет выбран самый большой город из возможных_cities. После долгих исследований было определено, что это даст наиболее точный город для каждого заголовка; Например, в заголовке «Коронавирус в Нью-Йорке убивает сотни людей», Нью-Йорк и Йорк являются вполне правдоподобными городами, но Нью-Йорк будет выбран как лучший город из двух.
Далее, из уже составленных названий стран, возможные страны определяются из заголовков. Это очень похоже на процесс в городах. Однако одно ключевое отличие состоит в том, что Грузия и Джерси намеренно исключены как страны; после долгих исследований этот вариант был снова выбран как наиболее оптимальный, поскольку сохранение этих стран могло бы противоречить результатам штатов. Аналогичный процесс снова применяется для округов и штатов.
Наконец, если страны, города, штаты или страны не найдены с помощью шагов, показанных выше, значение NaN помещается в набор данных, чтобы указать, что для этого нет соответствующего значения. Фрейм данных со столбцами «заголовок», «страны», «города», «штаты» и «страны» сделан из словаря для упрощения работы.
dictionary = {'headline':[], 'countries':[], 'cities':[], 'states':[], 'counties':[]} for i in headlines: dictionary['headline'].append(i) # Removing Special Characters i = unidecode.unidecode(i) words = i.split() for x in range(len(words)): if (not words[x][-1].isalnum()): words[x] = words[x][:-1] # Checking whether the phrase extracted is the name of a city for j in range(len(words)): poss_cities = [] string = words[j] if (gnc.get_cities_by_name(words[j]) != []): poss_cities.append(string) for k in range(j+1, len(words)): string += " " + words[k] if (gnc.get_cities_by_name(string) != []): poss_cities.append(string) if (len(poss_cities) != 0): dictionary['cities'].append(poss_cities[-1]) break # Checking whether the phrase extracted is the name of a country (excluding Georgia and Jersey) for j in range(len(words)): poss_countries = [] string = words[j] if (string in country_names): poss_countries.append(string) if (string in list(country_counter.keys())) and (string != "Georgia") and (string != "Jersey"): country_counter[string]+=1 for k in range(j+1, len(words)): string += " " + words[k] if (string in country_names): poss_countries.append(string) if (string in list(country_counter.keys())) and (string != "Georgia") and (string != "Jersey"): country_counter[string]+=1 if (len(poss_countries) != 0): dictionary['countries'].append(poss_countries[-1]) break # Checking whether the phrase extracted is the name of a county for j in range(len(words)): list_of_counties = list(county_names.keys()) poss_counties = [] string = words[j] if (string in list_of_counties): poss_counties.append(string) if string in list(county_counter.keys()): county_counter[string]+=1 for k in range(j+1, len(words)): string += " " + words[k] if (string in list_of_counties): poss_counties.append(string) if string in list(county_counter.keys()): county_counter[string]+=1 if (len(poss_counties) != 0): dictionary['counties'].append(poss_counties[-1]) dictionary['states'].append(state_keys[county_names[poss_counties[-1]]]) break # Checking whether the phrase extracted is the name of a state for j in range(len(words)): poss_states = [] string = words[j] if (string in state_names): poss_states.append(string) if string in list(state_counter.keys()): state_counter[string]+=1 for k in range(j+1, len(words)): string += " " + words[k] if (string in state_names): poss_states.append(string) if string in list(state_counter.keys()): state_counter[string]+=1 if (len(poss_states) != 0 and len(dictionary['headline']) != len(dictionary['states'])): dictionary['states'].append(poss_states[-1]) break # Making the country United States if there is a corresponding state or county if (len(dictionary['headline']) != len(dictionary['countries'])): if (len(dictionary['headline']) == len(dictionary['states'])) or (len(dictionary['headline']) == len(dictionary['counties'])): dictionary['countries'].append('United States') country_counter['United States']+=1 else: dictionary['countries'].append(np.nan) # Appending NaN values if the headline doesn't have a corresponding city, country, state, or county if (len(dictionary['headline']) != len(dictionary['cities'])): dictionary['cities'].append(np.nan) if (len(dictionary['headline']) != len(dictionary['counties'])): dictionary['counties'].append(np.nan) if (len(dictionary['headline']) != len(dictionary['states'])): dictionary['states'].append(np.nan) while (len(dictionary['states']) > len(dictionary['headline'])): dictionary['states'].pop() df = pd.DataFrame(data = dictionary)
Шаг 6. Определение наиболее пострадавших стран, штатов и округов
Словари state_counter, county_counter и country_counter преобразуются в фреймы данных и сортируются по количеству, чтобы найти наиболее пострадавшие и наименее пострадавшие страны, штаты и округа.
def convert_dict_to_df(dict1, value): dictionary_counter = {value: [], 'Count':[]} for index in list(dict1.keys()): dictionary_counter[value].append(index) dictionary_counter['Count'].append(dict1[index]) df = pd.DataFrame(data = dictionary_counter, columns = [value, 'Count']) df = df.sort_values(by = ['Count'], ascending = False) df = df.reset_index() df.index += 1 del df['index'] return df df_state = convert_dict_to_df(state_counter, 'State') df_county = convert_dict_to_df(county_counter, 'County') df_country = convert_dict_to_df(country_counter, 'Country')
Шаг 7. Определение широты и долготы для каждого заголовка
Сначала для каждого штата вручную вводились широта и долгота. Для стран и округов широта и долгота были взяты из существующих наборов данных и сохранены в словаре. Затем выполняется итерация фрейма данных для определения широты и долготы.
gnc = geonamescache.GeonamesCache() states_lat_long = { 'Alabama': [32.806671, -86.791130], 'Alaska': [61.370716, -152.404419], 'Arizona': [33.729759, -111.431221], 'Arkansas': [34.969704, -92.373123], 'California': [36.116203, -119.618564], 'Colorado': [39.059811, -105.311104], 'Connecticut': [41.597782, -72.755371], 'Delaware': [39.318523, -75.507141], 'District of Columbia': [38.897438, -77.026817], 'Florida': [27.766279, -81.686783], 'Georgia': [33.040619, -83.643074], 'Hawaii': [21.094318, -157.498337], 'Idaho': [44.240459, -114.478828], 'Illinois': [40.349457, -88.986137], 'Indiana': [39.849426, -86.258278], 'Iowa': [42.011539, -93.210526], 'Kansas': [38.526600, -96.726486], 'Kentucky': [37.668140, -84.670067], 'Louisiana': [31.169546, -91.867805], 'Maine': [44.693947, -69.381927], 'Maryland': [39.063946, -76.802101], 'Massachusetts': [42.230171, -71.530106], 'Michigan': [43.326618, -84.536095], 'Minnesota': [45.694454, -93.900192], 'Mississippi': [32.741646, -89.678696], 'Missouri': [38.456085, -92.288368], 'Montana': [46.921925, -110.454353], 'Nebraska': [41.125370, -98.268082], 'Nevada': [38.313515, -117.055374], 'New Hampshire': [43.452492, -71.563896], 'New Jersey': [40.298904, -74.521011], 'New Mexico': [34.840515, -106.248482], 'New York': [42.165726, -74.948051], 'North Carolina': [35.630066, -79.806419], 'North Dakota': [47.528912, -99.784012], 'Ohio': [40.388783, -82.764915], 'Oklahoma': [35.565342, -96.928917], 'Oregon': [44.572021, -122.070938], 'Pennsylvania': [40.590752, -77.209755], 'Rhode Island': [41.680893, -71.511780], 'South Carolina': [33.856892, -80.945007], 'South Dakota': [44.299782, -99.438828], 'Tennessee': [35.747845, -86.692345], 'Texas': [31.054487, -97.563461], 'Utah': [40.150032, -111.862434], 'Vermont': [44.045876, -72.710686], 'Virginia': [37.769337, -78.169968], 'Washington': [47.400902, -121.490494], 'West Virginia': [38.491226, -80.954453], 'Wisconsin': [44.268543, -89.616508], 'Wyoming': [42.755966, -107.302490] } file2 = open('county_lat_long.txt', 'r') counties = file2.readlines() county_data = {} for i in range(50, len(counties)): county_values = counties[i].strip().split('\t') county_data[county_values[3]] = [county_values[-2], county_values[-1]] file2 = open('country_lat_long.txt', 'r') countries = file2.readlines() country_data = {} for i in range(len(countries)): country_values = countries[i].strip().split('\t') country_data[country_values[-1]] = [country_values[-3], country_values[-2]]
Шаг 8: Удаление городов с низкой плотностью населения
Во-первых, заботятся о значениях «NaN». Широта и долгота сначала проверялись для города, затем округа, затем штата, а затем страны. Поскольку есть несколько мест с одним и тем же названием, был добавлен город с наибольшим населением. Кроме того, были исключены города с населением менее 50 000 человек, поскольку маловероятно, что эти города будут упомянуты авторитетным источником новостей. Широта, долгота и соответствующий код страны добавляются в фрейм данных.
latitude = [] longitude = [] country_code = [] for index_val in df1.index: city = df1['cities'][index_val] state = df1['states'][index_val] country = df1['countries'][index_val] county = df1['counties'][index_val] val = gnc.get_cities_by_name(city) # Listing the order of priority: city, county, state, then country if (city == np.nan or val == []): if (county in list(county_counter.keys())): latitude.append(county_data[county][0]) longitude.append(county_data[county][1]) else: if (state in state_names): latitude.append(states_lat_long[state][0]) longitude.append(states_lat_long[state][1]) else: if (country in list(country_data.keys())): latitude.append(float(country_data[country][0])) longitude.append(float(country_data[country][1])) else: latitude.append(np.nan) longitude.append(np.nan) country_code.append(np.nan) else: # Extracting places with a population of more than 50,000 (excluding Latina and York) maxpop = 0 index = 0 for j in range(len(val)): keys = [e for e in val[j]] population = val[j][keys[0]]['population'] if (population > maxpop): maxpop = population index = j keys = [e for e in val[index]] if (maxpop <= 50000) or (city == "York") or (city == "Latina" and country != "Spain"): latitude.append(np.nan) longitude.append(np.nan) country_code.append(np.nan) df1.loc[df1.index == index_val, 'cities']=np.nan else: latitude.append(val[index][keys[0]]['latitude']) longitude.append(val[index][keys[0]]['longitude']) country_code.append(val[index][keys[0]]['countrycode']) #print(index) if (str(city) == str(state)): df1.loc[df1.index == index_val, 'cities']=np.nan if (str(country) == str(city)): df1.loc[df1.index == index_val, 'cities']=np.nan latitude = [float(i) for i in latitude] longitude = [float(i) for i in longitude] df1['latitude'] = latitude df1['longitude'] = longitude df1['countrycode'] = country_code df = df1.dropna(subset = ['latitude', 'longitude']) df = df.reset_index() del df['index']
Шаг 9. Разделение фрейма данных
Во-первых, фрейм данных делится на четыре фрейма данных: df_us (фрейм данных, содержащий заголовки, относящиеся к США), df_no_us (фрейм данных, содержащий заголовки, относящиеся к США, с только «широтой» и «долготой» в качестве столбцов), df_world (исходный фрейм данных) и df_no_world (исходный фрейм данных с только «широтой» и «долготой» в качестве столбцов).
df_us = {'headline':[], 'cities':[], 'latitude':[], 'counties': [], 'states': [], 'countries': [], 'longitude':[], 'countrycode':[]} df_world = df df_no_us = {'latitude':[], 'longitude':[]} df_no_world = {'latitude':[], 'longitude':[]} for index in df.index: if (df['countries'][index] == "United States"): for column in list(df.columns): df_us[column].append(df[column][index]) for column in ['latitude', 'longitude']: df_no_us[column].append(df[column][index]) for column in ['latitude', 'longitude']: df_no_world[column].append(df[column][index]) f_us = pd.DataFrame(data = df_us) df_no_us = pd.DataFrame(data = df_no_us) df_no_world = pd.DataFrame(data = df_no_world) df_us
Шаг 9. Построение кривой изгиба для определения количества кластеров
Кривая изгиба нанесена на график для определения оптимального значения кластера в диапазоне от одного до пятидесяти кластеров. После изучения изгиба кривой было обнаружено, что тридцать кластеров являются оптимальным значением как для американских, так и для мировых наборов данных.
def elbow_curve(df1): clusters = range(1, 50) kmeans_elbow = [KMeans(n_clusters=i) for i in clusters] score = [kmeans_elbow[i].fit(df1).score(df1) for i in range(len(kmeans_elbow))] plt.plot(clusters, score) plt.xlabel('Number of Clusters') plt.ylabel('Score') plt.title('Elbow Curve') plt.show() elbow_curve(df_no_us) elbow_curve(df_no_world)
Шаг 10. Запуск алгоритма K-средних
Используя библиотеку sklearn, была создана функция, которая реализует алгоритм кластеризации k-средних по столбцам широты и долготы фрейма данных. Кроме того, для облегчения работы было выбрано заранее определенное количество кластеров США и мира. Функция запускалась с фреймами данных США и мира.
def run_k_means(df1, num_cluster, printGraph): #Adding to Dataframe kmeans_elbow = KMeans(n_clusters=num_cluster-1) df1["cluster_label"] = kmeans_elbow.fit(df1).labels_ if (printGraph): kmeans = KMeans(n_clusters=num_cluster).fit(df1) centroids = kmeans.cluster_centers_ plt.scatter(df1['latitude'], df1['longitude'], c= kmeans.labels_.astype(float), s=50, alpha=0.5) plt.scatter(centroids[:, 0], centroids[:, 1], c='red', s=50) plt.show() us_clusters = 30 run_k_means(df_no_us, us_clusters, True) world_clusters = 30 run_k_means(df_no_world, world_clusters, True)
Щелкните эту ссылку для доступа к репозиторию Github с подробным объяснением кода: Github.