В каком городе Великобритании самые безопасные водители?
Делиться

В этой статье я покажу вам, как использовать две популярные библиотеки Python для проведения геопространственного анализа данных о дорожно-транспортных происшествиях в Великобритании.
Я одним из первых начал использовать DuckDB , быструю OLAP-базу данных, после её появления, но лишь недавно понял, что с помощью расширения она предлагает большое количество потенциально полезных геопространственных функций.
Библиотека Geopandas была для меня чем-то новым. Это библиотека Python, которая позволяет работать с географическими данными так же, как и с обычной библиотекой pandas, но с встроенной геометрией (точками, линиями, полигонами). Вы можете читать/записывать стандартные форматы ГИС (GeoJSON, Shapefile, GeoPackage), манипулировать атрибутами и геометрией одновременно, а также быстро визуализировать слои с помощью Matplotlib.
Желая опробовать возможности обеих программ, я приступил к поиску полезного мини-проекта, который был бы одновременно интересным и полезным опытом обучения.
Короче говоря, я решил попробовать использовать обе библиотеки, чтобы определить самый безопасный город в Великобритании для вождения или пеших прогулок. Возможно, вы могли бы сделать все, что я сейчас покажу, используя Geopandas самостоятельно, но, к сожалению, я не так хорошо знаком с ней, как с DuckDB, поэтому я использовал обе.
Несколько основных правил.
Данные о дорожно-транспортных происшествиях и пострадавших, которые я буду использовать, взяты из официального источника правительства Великобритании и охватывают всю страну. Однако я сосредоточусь только на данных по шести крупнейшим городам Великобритании: Лондону, Эдинбургу, Кардиффу, Глазго, Бирмингему и Манчестеру.
Мой метод определения «самого безопасного» города будет заключаться в подсчете общего числа дорожно-транспортных происшествий в каждом городе за пятилетний период и делении этого числа на площадь каждого города в км². Это будет мой «индекс безопасности», и чем ниже это число, тем безопаснее город.
Получение данных о границах нашего города.
Это, пожалуй, была самая сложная часть всего процесса.
Вместо того чтобы рассматривать «город» как единый административный многоугольник, что приводит к различным аномалиям в отношении городских площадей, я смоделировал каждый из них по его застроенной территории (ЗЗТ) . Я сделал это, используя слой данных карты застроенных территорий Управления национальной статистики (ONS), а затем объединил все части ЗЗТ, которые попадают в разумные административные границы для данного местоположения. Маска взята из официальных границ ONS и выбрана таким образом, чтобы отражать каждую более широкую городскую территорию:
- Лондон → Лондонский регион (Регионы, декабрь 2023 г.).
- Манчестер → объединение 10 районов местного самоуправления Большого Манчестера (LAD) в мае 2024 года.
- Бирмингем → объединение 7 объединенных административных округов Западного Мидлендса.
- Глазго → объединение 8 советов Глазгоского городского региона.
- Эдинбург и Кардифф → их единственный сингл LAD.
Как создаются многоугольники в коде
Я загрузил данные о границах непосредственно из ArcGIS FeatureServer Управления национальной статистики Великобритании (ONS) в формате GeoJSON , используя библиотеку requests. Для каждого города мы сначала создали маску из официальных административных слоев ONS: Лондонский регион (декабрь 2023 г.) для Лондона; объединение районов местных органов власти (LAD, май 2024 г.) для Большого Манчестера (10 LAD), ядро Западного Мидлендса (7 LAD) и городской регион Глазго (8 советов); и единый LAD для Эдинбурга и Кардиффа.
Далее я выполняю запрос к слою ONS Built-Up Areas (BUA 2022) для поиска полигонов, пересекающих ограничивающий прямоугольник маски, сохраняя только те, которые пересекают маску, и объединяю (сливаю) результаты, чтобы создать единый составной полигон для каждого города («агрегированный BUA 2022»). Данные хранятся и отображаются в EPSG:4326 (WGS84) . То есть широта и долгота выражаются в градусах. При указании площадей мы перепроецируем их в EPSG:27700 (OSGB) и рассчитываем площадь в квадратных километрах, чтобы избежать искажений.
Данные о границах каждого города загружаются в файл GeoJSON и загружаются в Python с помощью библиотек geopandas и requests.
Чтобы показать корректность имеющихся у нас данных о границах, отдельные слои городов объединяются в единый геофрейм данных, перепроецируются в согласованную систему координат (EPSG:4326) и накладываются на четкий контур Великобритании, полученный из набора данных Natural Earth (через зеркало GitHub). Чтобы сосредоточиться только на материковой части, мы обрезаем контур Великобритании до ограничивающего прямоугольника городов, исключая отдаленные заморские территории. Также рассчитывается площадь каждого города.
Лицензирование граничных данных
Все использованные мной наборы данных о границах являются открытыми данными с условиями, допускающими повторное использование.
Лондон, Эдинбург, Кардифф, Глазго, Бирмингем и Манчестер
- Источник: Управление национальной статистики (ONS) — Графства и унитарные органы власти (май 2023 г.), Великобритания BGC и регионы (декабрь 2023 г.) EN BGC.
- Лицензия: Открытая государственная лицензия версии 3.0 (OGL) .
- Условия: Вы можете свободно использовать, изменять и распространять данные (в том числе в коммерческих целях) при условии указания авторства.
Контур Великобритании
- Источник: Natural Earth — Администратор 0 — Страны.
- Лицензия: Общественное достояние (без ограничений) . Цитирование:
- Natural Earth. Администратор 0 — Страны. Общественное достояние. Доступно по адресу: https://www.naturalearthdata.com
код границ города
Вот код на Python, который можно использовать для загрузки файлов данных по каждому городу и проверки их границ на карте.
Перед запуском основного кода убедитесь, что у вас установлены следующие библиотеки. Для этого можно использовать pip или другой удобный для вас способ.
import geopandas matplotlib requests pandas duckdb jupyter import requests, json import geopandas as gpd import pandas as pd import matplotlib.pyplot as plt # ———- Конечные точки ONS ———- LAD24_FS = «https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Local_Authority_Districts_May_2024_Boundaries_UK_BGC/FeatureServer/0/query» REGION23_FS = «https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Regions_December_2023_Boundaries_EN_BGC/FeatureServer/0/query» BUA_FS = «https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/BUA_2022_GB/FeatureServer/0/query» # ———- Вспомогательные функции ———- def arcgis_geojson(url, params): r = requests.get(url, params={**params, «f»: «geojson»}, timeout=90) r.raise_for_status() return r.json() def sql_quote(s: str) -> str: return «'» + s.replace(«'», «''») + «'» def fetch_lads_by_names(names): where = «LAD24NM IN ({})».format(«,».join(sql_quote(n) for n in names)) data = arcgis_geojson(LAD24_FS, { «where»: where, «outFields»: «LAD24NM», «returnGeometry»: «true», «outSR»: «4326» }) gdf = gpd.GeoDataFrame.from_features(data.get(«features», []), crs=»EPSG:4326″) if gdf.empty: raise RuntimeError(f»No LADs found for: {names}») return gdf def fetch_lad_by_name(name): return fetch_lads_by_names([name]) def fetch_region_by_name(name): data = arcgis_geojson(REGION23_FS, { «where»: f»RGN23NM={sql_quote(name)}», «outFields»: «RGN23NM», «returnGeometry»: «true», «outSR»: «4326» }) gdf = gpd.GeoDataFrame.from_features(data.get(«features», []), crs=»EPSG:4326″) if gdf.empty: raise RuntimeError(f»No Region feature for: {name}») return gdf def fetch_buas_intersecting_bbox(minx, miny, maxx, maxy): data = arcgis_geojson(BUA_FS, { «where»: «1=1», «geometryType»: «esriGeometryEnvelope», «geometry»: json.dumps({ «xmin»: float(minx), «ymin»: float(miny), «xmax»: float(maxx), «ymax»: float(maxy), «spatialReference»: {«wkid»: 4326} }), «inSR»: «4326», «spatialRel»: «esriSpatialRelIntersects», «outFields»: «BUA22NM,BUA22CD,Shape__Area», «returnGeometry»: «true», «outSR»: «4326» }) return gpd.GeoDataFrame.from_features(data.get(«features», []), crs=»EPSG:4326″) def aggregate_bua_by_mask(mask_gdf: gpd.GeoDataFrame, label: str) -> gpd.GeoDataFrame: «»» Получает полигоны BUA 2022, пересекающие маску (объединение LAD/региона), и объединяет их в один полигон. Использует функцию shapely 2.x union_all() для построения геометрии маски. «»» # Объединяем полигоны маски mask_union = mask_gdf.geometry.union_all() # Получение потенциальных BUA по ограничивающей рамке маски, затем фильтрация по реальному пересечению с объединением minx, miny, maxx, maxy = gpd.GeoSeries([mask_union], crs=»EPSG:4326″).total_bounds buas = fetch_buas_intersecting_bbox(minx, miny, maxx, maxy) if buas.empty: raise RuntimeError(f»Нет BUA, пересекающих ограничивающую рамку для {label}») buas = buas[buas.intersects(mask_union)] if buas.empty: raise RuntimeError(f»Нет BUA, фактически пересекающих маску для {label}») dissolved = buas[[«geometry»]].dissolve().reset_index(drop=True) dissolved[«city»] = label return dissolved[[«city», «geometry»]] # ———- Определения групп ———- GM_10 = [«Манчестер»,»Солфорд»,»Траффорд»,»Стокпорт»,»Теймсайд», «Олдхэм»,»Рочдейл»,»Бери»,»Болтон»,»Уиган»] WMCA_7 = [«Бирмингем»,»Ковентри»,»Дадли»,»Сэндвелл»,»Солихалл»,»Уолсолл»,»Вулверхэмптон»] GLASGOW_CR_8 = [«Глазго Сити»,»Восточный Данбартоншир»,»Западный Данбартоншир», «Восточный Ренфрушир»,»Ренфрушир»,»Инверклайд», «Северный Ланаркшир»,»Южный Ланаркшир»] EDINBURGH = «Город Эдинбург» CARDIFF = «Кардифф» # ———- Создание масок ———- london_region = fetch_region_by_name(«Лондон») # Маска региона для Лондона gm_lads = fetch_lads_by_names(GM_10) # Большой Манчестер (10) wmca_lads = fetch_lads_by_names(WMCA_7) # Западные Мидлендс (7) gcr_lads = fetch_lads_by_names(GLASGOW_CR_8) # Городской регион Глазго (8) edi_lad = fetch_lad_by_name(EDINBURGH) # Один LAD cdf_lad = fetch_lad_by_name(CARDIFF) # Один LAD # ———- Агрегирование BUA по каждой маске ———- layers = [] london_bua = aggregate_bua_by_mask(london_region, «London (BUA 2022 aggregate)») london_bua.to_file(«london_bua_aggregate.geojson», driver=»GeoJSON») Layers.append(london_bua) man_bua =агрегат_bua_by_mask(gm_lads, «Манчестер (агрегат BUA 2022)») «Бирмингем (агрегат BUA 2022)») bham_bua.to_file(«birmingham_bua_aggregate.geojson», driver=»GeoJSON») Layers.append(bham_bua) glas_bua =greg_bua_by_mask(gcr_lads, «Глазго (агрегат BUA 2022)»)) glas_bua.to_file(«glasgow_bua_aggregate.geojson», layers.append(glas_bua) edi_bua = aggregate_bua_by_mask(edi_lad, «Edinburgh (BUA 2022 aggregate)») edi_bua.to_file(«edinburgh_bua_aggregate.geojson», driver=»GeoJSON») layers.append(edi_bua) cdf_bua = aggregate_bua_by_mask(cdf_lad, «Cardiff (BUA 2022 aggregate)») cdf_bua.to_file(«cardiff_bua_aggregate.geojson», driver=»GeoJSON») layers.append(cdf_bua) # ———- Объединение и отчет по областям ———- cities = gpd.GeoDataFrame(pd.concat(layers, ignore_index=True), crs=»EPSG:4326″) # ———- Построение контура Великобритании + шесть агрегатов ———- # Контур Великобритании (Natural Earth 1:10m, простые страны) ne_url = «https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_admin_0_countries.geojson» world = gpd.read_file(ne_url) uk = world[world[«ADMIN»] == «United Kingdom»].to_crs(4326) # Обрезка рамки по нашим городам minx, miny, maxx, maxy = cities.total_bounds uk_crop = uk.cx[minx-5 : maxx+5, miny-5 : maxy+5] fig, ax = plt.subplots(figsize=(9, 10), dpi=150) uk_crop.boundary.plot(ax=ax, color=»black», linewidth=1.2) cities.plot(ax=ax, column=»city», alpha=0.45, edgecolor=»black», linewidth=0.8, legend=True) # Подписываем каждый многоугольник, используя внутреннюю точку label_pts = cities.representative_point() for (x, y), name in zip(label_pts.geometry.apply(lambda p: (px, py)), cities[«city»]): ax.text(x, y, name, fontsize=8, ha=»center», va=»center») ax.set_title(«BUA 2022 Aggregates: London, Manchester, Birmingham, Glasgow, Edinburgh, Cardiff», fontsize=12) ax.set_xlim(minx-1, maxx+1) ax.set_ylim(miny-1, maxy+1) ax.set_aspect(«equal», adjustable=»box») ax.set_xlabel(«Longitude»); ax.set_ylabel(«Latitude») plt.tight_layout() plt.show()
После выполнения этого кода в текущей директории должно быть сохранено 6 файлов GeoJSON, а также должен отобразиться примерно такой результат, который наглядно показывает, что файлы с границами городов содержат корректные данные.

Получение данных о наших авариях
Последний элемент нашей информационной головоломки — это данные об авариях. Правительство Великобритании публикует отчеты о данных по дорожно-транспортным происшествиям за пятилетние периоды. Последние данные охватывают период с 2019 по 2024 год. Этот набор данных охватывает всю Великобританию, поэтому нам потребуется обработать его, чтобы извлечь только данные по шести интересующим нас городам. Здесь нам поможет DuckDB, но об этом позже.
Для просмотра или загрузки данных об авариях в формате CSV (Источник: Министерство транспорта — Данные о безопасности дорожного движения) перейдите по ссылке ниже.
https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-last-5-years.csv
Как и данные о границах городов, эта информация также публикуется под лицензией Open Government Licence v3.0 (OGL 3.0) и, следовательно, имеет те же условия лицензирования.
Набор данных об авариях содержит большое количество полей, но для наших целей нас интересуют только 3 из них:
широта долгота количество_пострадавших
Получение данных о количестве жертв в каждом городе теперь осуществляется всего в три этапа.
1/ Загрузка набора данных об авариях в DuckDB
Если вы никогда раньше не сталкивались с DuckDB, то вкратце это сверхбыстрая база данных, работающая в оперативной памяти (а также с возможностью постоянного хранения данных), написанная на C++ и предназначенная для аналитических задач SQL.
Одна из главных причин, почему она мне нравится, — это её скорость. Это одна из самых быстрых сторонних библиотек для анализа данных, которые я использовал. Она также очень расширяема за счёт использования расширений, таких как геопространственное расширение, которое мы сейчас и будем использовать.
Теперь мы можем загрузить данные об авариях следующим образом.
import requests import duckdb # Удаленный CSV-файл (столкновения за последние 5 лет) url = «https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-last-5-years.csv» local_file = «collisions_5yr.csv» # Загрузка файла r = requests.get(url, stream=True) r.raise_for_status() with open(local_file, «wb») as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) print(f»Загружено {local_file}») # Подключение один раз con = duckdb.connect(database=':memory:') # Установка и загрузка пространственных данных по ЭТОМУ соединению con.execute(«УСТАНОВКА пространственных данных;») con.execute(«ЗАГРУЗКА пространственных данных;») # Создание таблицы аварий с геометрией con.execute(«»» CREATE TABLE accidents AS SELECT TRY_CAST(Latitude AS DOUBLE) AS latitude, TRY_CAST(Longitude AS DOUBLE) AS longitude, TRY_CAST(Number_of_Casualties AS INTEGER) AS number_of_casualties, ST_Point(TRY_CAST(Longitude AS DOUBLE), TRY_CAST(Latitude AS DOUBLE)) AS geom FROM read_csv_auto('collisions_5yr.csv', header=True, nullstr='NULL') WHERE TRY_CAST(Latitude AS DOUBLE) IS NOT NULL AND TRY_CAST(Longitude AS DOUBLE) IS NOT NULL AND TRY_CAST(Number_of_Casualties AS INTEGER) IS NOT NULL «»») # Быстрый предварительный просмотр print(con.execute(«DESCRIBE accidents»).df()) print(con.execute(«SELECT * FROM accidents LIMIT 5»).df())
Вы должны увидеть следующий результат.
Загружен файл collisions_5yr.csv column_name column_type null key default extra 0 latitude DOUBLE YES None None None 1 longitude DOUBLE YES None None None 2 number_of_casualties INTEGER YES None None None 3 geom GEOMETRY YES None None None latitude longitude number_of_casualties 0 51.508057 -0.153842 3 1 51.436208 -0.127949 1 2 51.526795 -0.124193 1 3 51.546387 -0.191044 1 4 51.541121 -0.200064 2 geom 0 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 1 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 2 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 3 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, … 4 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, …
2/ Загрузка данных о границах города с использованием пространственных функций DuckDB.
Для этого мы будем использовать функцию ST_READ, которая может читать и импортировать различные форматы геопространственных файлов с помощью библиотеки GDAL.
city_files = { «London»: «london_bua_aggregate.geojson», «Edinburgh»: «edinburgh_bua_aggregate.geojson», «Cardiff»: «cardiff_bua_aggregate.geojson», «Glasgow»: «glasgow_bua_aggregate.geojson», «Manchester»: «manchester_bua_aggregate.geojson», «Birmingham»: «birmingham_bua_aggregate.geojson» } for city, file in city_files.items(): con.execute(f»»» CREATE TABLE {city.lower()} AS SELECT '{city}' AS city, geom FROM ST_Read('{file}') «»») con.execute(«»» CREATE TABLE cities AS SELECT * FROM london UNION ALL SELECT * FROM Эдинбург UNION ALL SELECT * FROM Кардифф UNION ALL SELECT * FROM Глазго UNION ALL SELECT * FROM Манчестер UNION ALL SELECT * FROM Бирмингем «»»)
3/ Следующий шаг — привязать места аварий к городским полигонам и подсчитать число жертв.
Ключевая геопространственная функция, которую мы используем в этот раз, называется ST_WITHIN. Она возвращает TRUE, если первая геометрическая точка находится в пределах границ второй.
import duckdb casualties_per_city = con.execute(«»» SELECT c.city, SUM(a.number_of_casualties) AS total_casualties, COUNT(*) AS accident_count FROM accidents a JOIN cities c ON ST_Within(a.geom, c.geom) GROUP BY c.city ORDER BY total_casualties DESC «»»).df() print(«Casualties per city:») print(casualties_per_city)
Обратите внимание, что я выполнял указанный выше запрос на мощном настольном компьютере, и даже в этом случае получение результатов заняло несколько минут, поэтому, пожалуйста, наберитесь терпения. Однако в конечном итоге вы должны увидеть результат, похожий на этот.
Число жертв по городам: город общее количество жертв количество аварий 0 Лондон 134328.0 115697 1 Бирмингем 14946.0 11119 2 Манчестер 4518.0 3502 3 Глазго 3978.0 3136 4 Эдинбург 3116.0 2600 5 Кардифф 1903.0 1523
Анализ
Неудивительно, что в Лондоне в целом зафиксировано наибольшее количество жертв. Но учитывая его размеры, насколько опасно в нем ездить на автомобиле или передвигаться пешком по сравнению с другими городами?
В данных о количестве жертв в Манчестере и Глазго явно есть проблема. Исходя из размеров городов, эти показатели должны быть выше. Предполагается, что это может быть связано с тем, что многие из оживленных кольцевых дорог и прилегающих к метро дорог (M8/M74/M77; M60/M62/M56/M61), связанных с каждым из этих городов, находятся за пределами их узких полигонов BUA, что приводит к значительному занижению данных об авариях и жертвах. Я оставляю это исследование на усмотрение читателя!
Для окончательной оценки безопасности дорожного движения нам необходимо знать площадь каждого города, чтобы рассчитать коэффициент смертности на квадратный километр.
К счастью, в DuckDB есть для этого функция. ST_AREA вычисляет площадь геометрического объекта.
# — Вычисление площадей в км² (CRS84 -> OSGB 27700) — print(«nВычисление площадей в км²…») areas = con.execute(«»» SELECT city, ST_Area( ST_MakeValid( ST_Transform( — ST_Simplify(geom, 0.001), — Эксперимент со значением эпсилон (например, 0.001 градуса) geom, 'OGC:CRS84','EPSG:27700' ) ) ) / 1e6 AS area_km2 FROM cities ORDER BY area_km2 DESC; «»»).df() print(«Площади городов:») print(areas.round(2))
Я получил следующий результат, который, по-видимому, является вполне правильным.
Площадь города (км²): 0 Лондон 1321,45 1 Бирмингем 677,06 2 Манчестер 640,54 3 Глазго 481,49 4 Эдинбург 123,00 5 Кардифф 96,08
Теперь у нас есть все необходимые данные, чтобы определить, в каком городе Великобритании самые безопасные водители. Помните, чем ниже значение «индекса безопасности», тем безопаснее езда.
Город Площадь_км2 Пострадавшие Индекс безопасности (пострадавшие/площадь) 0 Лондон 1321,45 134328 101,65 1 Бирмингем 677,06 14946 22,07 2 Манчестер 640,54 4518 7,05 3 Глазго 481,49 3978 8,26 4 Эдинбург 123,00 3116 25,33 5 Кардифф 96,08 1903 19,08
Я не могу включить результаты по Манчестеру и Глазго из-за сомнений в достоверности данных о жертвах, о которых мы упоминали ранее.
Учитывая все вышесказанное, и поскольку я являюсь автором этой статьи, я объявляю Кардифф победителем в номинации «самый безопасный город» с точки зрения водителей и пешеходов. Что вы думаете об этих результатах? Живете ли вы в одном из городов, которые я рассматривал? Если да, подтверждают ли результаты ваш опыт вождения или передвижения пешком в этих городах?
Краткое содержание
Мы изучили возможность проведения разведочного анализа данных на основе геопространственного набора данных. Наша цель состояла в том, чтобы определить, какие из шести ведущих городов Великобритании являются наиболее безопасными для вождения автомобиля или передвижения пешком. Используя комбинацию GeoPandas и DuckDB, мы смогли:
- Используйте Geopandas для загрузки данных о границах городов с официального правительственного веб-сайта, которые достаточно точно отражают размер каждого города.
- Загрузите и обработайте обширный CSV-файл с данными о дорожно-транспортных происшествиях за 5 лет, чтобы получить три интересующих вас поля: широту, долготу и количество пострадавших в ДТП.
- Объедините данные об авариях с данными о границах городов по широте/долготе, используя геопространственные функции DuckDB, чтобы получить общее количество жертв в каждом городе за 5 лет.
- Для расчета размера в км² использовались геопространственные функции DuckDB. каждого города.
- Для каждого города был рассчитан индекс безопасности, представляющий собой отношение числа жертв в каждом городе к его размеру. Мы исключили два из наших результатов из-за сомнений в точности некоторых данных. Мы подсчитали, что Кардифф имел самый низкий показатель индекса безопасности и, следовательно, был признан самым безопасным из обследованных нами городов.
При наличии соответствующих входных данных геопространственный анализ с использованием описанных мною инструментов может стать неоценимым подспорьем во многих отраслях. Вспомним, например, анализ дорожно-транспортных происшествий (как я показал), анализ наводнений, вырубки лесов, лесных пожаров и засух. В сущности, любая система или процесс, связанные с пространственной системой координат, идеально подходят для исследовательского анализа данных любым желающим.
Источник: towardsdatascience.com























