Jeżeli kiedykolwiek zastanawialiście się jak w łatwy i przyjemny sposób przedstawić duży zbiór danych na mapie to w poniższym wpisie znajdziecie na to gotowe rozwiązanie. Ponadto w trakcie naszych analiz sprawdziliśmy, gdzie reprezentacja Polski rozegrała mecze w latach 2000 – 2020 i gdzie zwyciężyła. Do badań użyliśmy moduł Python Folium, który jest wrapperem do biblioteki Leaflet.js. W naszej ocenie w sposób znaczący ułatwia on wizualizację danych na mapie. Do obróbki zbioru danych użyliśmy popularny moduł Python o nazwie Pandas.
Źródła danych
[1] Dane międzynarodowych meczy piłki nożnej, rozgrywanych w latach 1872 – 2020
[2] Zbiór danych zawierający szerokość i długość geograficzną miast
Kluczowym do naszej analizy był [1] zbiór danych zawierający dane dotyczące meczów piłki nożnej, rozegranych w latach 1872-2020. Dwa pozostałe zbiory danych [2] i [3] użyliśmy do zamiany skrótów państw na współrzędne geograficzne lokalizacji w których rozgrywane były mecze.
Import modułów
import pandas as pd import folium from folium import Marker from folium.plugins import MarkerCluster
Import danych z plików CSV
Do wczytania danych z plików CSV służy metoda read_csv i jako parametr podawana jest w niej ścieżka do pliku. Dane wczytane w ten sposób zapisywane są w pamięci w obiekcie zwanym DataFrame. Jest to dwuwymiarowa struktura tabelaryczna.
cities = pd.read_csv('./worldcitiespop.csv') matches = pd.read_csv('./results.csv') abreviations = pd.read_csv('./data.csv')
Oczyszczanie danych
Zbiór danych 'cities’ [2] zawiera długość i szerokość geograficzną wszystkich miast na świecie. Niestety w kolumnie „państwo” znajduję się jedynie skrót nazwy. Dlatego do zamiany skrótów na pełne nazwy państw, zdecydowaliśmy się wykorzystać zbiór [3]. Przed scaleniem zbiorów trzeba było jeszcze doprowadzić skróty do jednakowej postaci, więc w [3] zapisaliśmy je małymi literami.
abreviations['Code'] = abreviations['Code'].str.lower()
Scalenie zbiorów wykonuję się metodą merge, podając przy tym sposób scalenia (left, right, outer, inner) oraz kolumny po których ma ono nastąpić.
countries_cities = cities.merge(abreviations, how='left', left_on='Country', right_on='Code')
Następnie w nowym DataFrame zostawimy tylko kolumny, które będą nam potrzebne do dalszej pracy.
countries_cities = countries_cities[['AccentCity','Name','Latitude','Longitude']]
W celu ujednolicenia zapisów w zbiorach, zapisaliśmy kolumnę „city” w zbiorze [1] matches oraz kolumnę „AccentCity” w nowym zbiorze countries_cities małymi literami.
matches['city'] = matches['city'].str.lower() countries_cities['AccentCity'] = countries_cities['AccentCity'].str.lower()
Po weryfikacji okazało się, że niektóre państwa i miasta w zbiorach zostały zapisane w różny sposób. Na przykład w zbiorze [1] zamiast państwa „Wielka Brytania” użyto nazw „Scotland”, „England”, „Northern Ireland”, „Ireland”, „Wales”, więc za pomocą metody replace, zmodyfikowaliśmy wszystkie różnice.
matches['country'] = matches['country'].str.replace('Scotland','United Kingdom') matches['country'] = matches['country'].str.replace('England','United Kingdom') matches['country'] = matches['country'].str.replace('Northern Ireland','United Kingdom') matches['country'] = matches['country'].str.replace('Ireland','United Kingdom') matches['country'] = matches['country'].str.replace('Wales','United Kingdom') matches['country'] = matches['country'].str.replace('Republic of United Kingdom','United Kingdom') matches['country'] = matches['country'].str.replace('Republic of Ireland','Ireland') matches['country'] = matches['country'].str.replace('Russia','Russian Federation') matches['country'] = matches['country'].str.replace('Bohemia','Czech Republic') matches['country'].loc[matches['city'] == 'dublin'] = matches['country'].loc[matches['city'] == 'dublin'].str.replace('United Kingdom','Ireland')
Po tych operacjach mogliśmy przeprowadzić ostatnie scalenie, czyli połączenie zbioru z wynikami meczy [1] matches z nowym zbiorem countries_cities, zawierającym wszystkie państwa, miasta i ich współrzędne geograficzne.
result= matches.merge(countries_cities, how='left', left_on=['city','country'], right_on=['AccentCity','Name']) result = result.rename(columns={'Latitude':'latitude', 'Longitude': 'longitude'}) #zapisanie nazw kolumn małymi literami
Po weryfikacji dataframe result okazało się, że istnieją jeszcze wiersze z pustymi wartościami w kolumnie „AccentCity”, spowodowane różnym zapisem nazw miast. Jednak postanowiliśmy, że już dalej nie będziemy uwspólniać zapisów w zbiorach i usunęliśmy puste wartości za pomocą metody notna .
result = result[result['AccentCity'].notna()]
W kolejnym kroku ograniczyliśmy analizowany zbiór meczy do tych, które zostały rozegrane w latach 2000-2020 oraz zresetowaliśmy indeks dataframe.
result = result[(result['date'] > '2000-01-01') & (result['date'] < '2020-11-01')] result = result.reset_index(drop=True) # reset indeksu dataframe
Wizualizacja danych na mapie
Po wstępnej obróbce zbiór danych był już gotowy do wizualizacji na mapie. Na początku przedstawiliśmy cały zbiór danych za pomocą znaczników.
worldMap = folium.Map(location=[result.loc[0,:].latitude,result.loc[0,:].longitude],zoom_start=5) for x, row in result.iterrows(): Marker([row['latitude'], row['longitude']]).add_to(worldMap)
W efekcie otrzymaliśmy mapę na której każdy znacznik reprezentuję pojedynczy mecz.
Jeżeli w Waszym edytorze kodu mapa się nie wyświetla to spróbujcie zapisać ją do pliku html.
worldMap.save("worldMap.html")
Mecze reprezentacji Polski w latach 2000-2020
Do dalszej analizy zredukowaliśmy zbiór tylko do meczów reprezentacji Polski oraz zresetowaliśmy indeks dataframe.
polish_matches = result[(result['home_team'] == 'Poland') | (result['away_team'] == 'Poland')] polish_matches = polish_matches.reset_index(drop=True) #reset indeksu dataframe
Na pierwszej mapie wyświetliliśmy dane w sposób analogiczny do poprzedniego.
polishMatchesMap = folium.Map(location=[polish_matches.loc[0,:].latitude,polish_matches.loc[0,:].longitude],zoom_start=5) for x, row in polish_matches.iterrows(): Marker([row['latitude'], row['longitude']]).add_to(polishMatchesMap)
Duża liczba znaczników powoduję, że mapa staje się nieczytelna. Rozwiązaniem tego problemu mogą być klastry, łączące pobliskie znaczniki w grupy i wyświetlające ich liczbę.
polishMatchesClusterMap = folium.Map(location=[polish_matches.loc[0,:].latitude,polish_matches.loc[0,:].longitude],zoom_start=5) markers = MarkerCluster() for x, row in polish_matches.iterrows(): markers.add_child(Marker([row['latitude'], row['longitude']])) polishMatchesClusterMap.add_child(markers)
Na koniec, jako ciekawostkę przedstawimy mapę z zaznaczonymi miejscami, gdzie reprezentacja Polski wygrała mecze w latach 2000-2020. Na początku wyświetliliśmy obecną postać dataframe polish_matches.
Wyniki meczów przechowywane są w kolumnach home_score oraz away_score, a więc zbiór trzeba przefiltrować:
1. Gdy Polska była drużyną gospodarzy i wynik gospodarzy był większy niż wynik gości (61 wierszy)
2. Gdy Polska była drużyną gości i wynik gości był większy niż gospodarzy. (54 wiersze)
Obydwa warunki w jednym wierszu można zapisać w sposób następujący.
polishWonmatches = polish_matches[((polish_matches['away_team'] == 'Poland') & (polish_matches['away_score'] > polish_matches['home_score'])) | (polish_matches['home_team'] == 'Poland') & (polish_matches['home_score'] > polish_matches['away_score'])] polishWonmatches = polishWonmatches.reset_index(drop=True) # reset indeksu dataframe
W efekcie otrzymaliśmy zbiór zawierający 115 wierszy. Na podstawie tej analizy dowiedzieliśmy się, że Polska w latach 2000-2020 rozegrała 278 meczów z czego 115 wygrała. Na koniec przedstawiliśmy miejsca wygranych meczów na mapie.
polishWonMatchesMapClusters = folium.Map(location=[polishWonmatches.loc[0,:].latitude,polishWonmatches.loc[0,:].longitude],zoom_start=5) markers = MarkerCluster() for x, row in polishWonmatches.iterrows(): markers.add_child(Marker([row['latitude'], row['longitude']])) polishWonMatchesMapClusters.add_child(markers)
Im większe zbliżenie tym widocznych jest więcej znaczników, a klastry zmniejszą swoją liczebność.