www.machinelearningmastery.ru

Машинное обучение, нейронные сети, искусственный интеллект
Header decor

Home

Картография командной строки для выборов в Великобритании

Дата публикации Nov 12, 2019

Этот учебник адаптирует шаги из@ mbostock-хРуководство по картографии из командной строкипоказать, как легко составить тематическую карту результатов выборов в Великобритании, используя бесплатные инструменты Javascript с открытым исходным кодом. Затем я повторяю часть, посвященную обработке данных, используя стек Python / Pandas / GeoPandas, чтобы сравнить простоту каждого подхода.

Это визуализация, которую мы сделаем:

Результаты всеобщих выборов 2017 года в Великобритании: победители по избирательным округам.

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

Получение геометрии границы

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

curl --progress-bar http://parlvid.mysociety.org/os/bdline_gb-2019-10.zip -O bdline_gb-2019-10.zip

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

unzip -o bdline_gb-2019-10.zip Data/GB/westminster_const_region.prj Data/GB/westminster_const_region.shp Data/GB/westminster_const_region.dbf Data/GB/westminster_const_region.shxmv Data/GB/westminster_const_region.* .

По предложению @mbostock, посещениеmapshaper.orgи перетаскиваяwestminster_const_region.shpв ваш браузер - хороший способ просмотреть то, что мы извлекли:

westminster_const_region.shp on mapshaper.org

Выглядит отлично, но округа Северной Ирландии отсутствуют. Оказывается, они доступны отдельно от Обзора артиллерии Северной Ирландии:

wget http://osni-spatial-ni.opendata.arcgis.com/datasets/563dc2ec3d9943428e3fe68966d40deb_3.zipunzip 563dc2ec3d9943428e3fe68966d40deb_3.zip

Получить настройки

Мы собираемся использовать серию инструментов командной строки Javascript, которыеМайк Бостокболее подробно описано в его уроке. Их можно установить с помощью следующих команд, если вы еще этого не сделали [Для работы следующего шага вам понадобится узел и npm]:

npm install -g shapefile          # gives us shp2json
npm install -g d3-geo-projection # geoproject, geo2svg
npm install -g topojson # geo2topo, topo2geo
npm install -g ndjson-cli
npm install -g d3

использованиеshp2jsonпреобразовать шейп-файлы в GeoJSON:

shp2json westminster_const_region.shp -o gb.json

Данные граничного файла Великобритании (ГБ) уже спроецированы наБританская национальная сеть(EPSG: 27700). Однако данные геометрии в границах Северной Ирландии (NI) определены вEPSG: 4326(WSG 84). Я пытался найти способ использоватьd3-geo-projectionчтобы преобразовать геометрию NI в EPSG: 27700, но я должен признать, что это было выше моего понимания. Вместо этого я вернулся к использованиюogr2ogrпреобразовать шейп-файл NI. Я на MacOS такogr2ogrможет быть установлен с помощьюзаваривать:

brew install gdal

Затем я перепроектировал шейп-файл из EPSG: 4326 в EPSG: 27700 следующим образом:

ogr2ogr -f "ESRI Shapefile" ni.shp OSNI_Open_Data_Largescale_Boundaries__Parliamentary_Constituencies_2008.shp -t_srs EPSG:27700 -s_srs EPSG:4326

Затем преобразуйте этот Shapefile в GeoJSON, как мы делали ранее с границами ГБ:

shp2json ni.shp -o ni.json

Объединение геометрии GB и NI

Мы преобразовали двоичные шейп-файлы в более читабельный GeoJSON. Но у нас все еще есть два отдельных файла. Было бы гораздо удобнее объединить все границы в одном файле.

Во второй части своего урока Майк представляетndjson-cli, его инструмент для создания и управления ndjson. Я подумал, что мы могли бы использовать этот инструмент, чтобы превратить наши файлы GeoJSON в json с разделителями строк и затем простоcatих вместе. Мне также нужно вытащить идентификатор избирателя и сделать его общим для каждого файла, используяndjson-map:

ndjson-split ‘d.features’ < gb.json \
| ndjson-map '{id: d.properties.CODE, properties:d.properties, type:d.type, geometry:d.geometry}' > gb_id.ndjsonndjson-split 'd.features' < ni.json \
| ndjson-map '{id: d.properties.PC_ID, properties:d.properties, type:d.type, geometry:d.geometry}' > ni_id.ndjsoncat gb_id.ndjson ni_id.ndjson > uk.ndjson

Я пытался работать с этого момента вперед сndjsonи потоковая геометрия вgeoprojectionно это не было успешным. Я счел необходимым преобразовать конкатенированные границы обратно в один объект JSON, используяndjson-reduceКоманда, прежде чем продолжить с прогнозами:

ndjson-reduce 'p.features.push(d), p' '{type: "FeatureCollection", features: []}' < uk.ndjson > uk.json

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

Использоватьgeoprojectинструмент с.fitsize()методd3.geoIdentity()сопоставить точки с ограничительной рамкой нашего выбора [Я также воспользовался возможностью перевернуть определения полигонов по вертикали, так как мы в конечном итоге будем отображать как SVG]:

geoproject 'd3.geoIdentity().reflectY(true).fitSize([960, 960], d)'\
< uk.json > uk-960.json

Теперь мы можем уменьшить размер нашего файла GeoJSON, переключившись на TopoJSON, а затемупрощение,квантующем,а такжесжатиеопределения полигонов. Пожалуйста прочтиЧасть 3изМайк БостокРуководство для более подробного объяснения этих шагов, которое я копирую ниже.

я использовалgeo2topoпреобразовать в TopoJSON, а затем использоватьtoposimplifyа такжеtopoquantizeИнструменты для уменьшения размера файла со 120М до 385К:

geo2topo const=uk-960.json \
| toposimplify -p 1 -f \
| topoquantize 1e5 \
> uk-simpl-topo.json

Теперь давайте взглянем на наш результат. Для создания SVG в командной строке мы можем использоватьgeo2svgинструменты. Сначала мы делаем преобразование обратно в GeoJSON, используяtopo2geo:

topo2geo const=- < uk-simpl-topo.json \
| geo2svg -p 1 -w 960 -h 960 > uk.svg

Файл SVG можно перетащить в веб-браузер, чтобы мы могли быстро его просмотреть

Соединенное Королевство Великобритании и Северной Ирландии в SVG через TopoJSON!

Добавление результатов выборов

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

Я нашел CSV, содержащий результаты выборов еще в 1918 году наСайт британского парламента:

wget http://researchbriefings.files.parliament.uk/documents/CBP-8647/1918-2017election_results.csv -O 1918_2017election_results.csv

Я использовал инструмент командной строкиgawkотфильтровать этот CSV, чтобы получить только результаты 2017 года и заполнить нулями нули. Сложность заключалась в том, что некоторые имена избирателей содержали запятые, которые нужно было игнорировать.

gawk -F ',' -v OFS=',' '{for (i=1;i<=NF;i++) { if ($i=="") $i="0" }; print}' < 1918_2017election_results.csv | gawk 'NR==1 {print}; { if ($18 == 2017) print}' FPAT='([^,]+)|("[^"]+")' > 2017_results.csv

Майк показывает, как соединить подобные данные с геометрией, сначала преобразовав ее в json с разделителями новой строки. Следуя его примеру, я использовалcsv2jsonи егоndjson-cliИнструменты для первого преобразования результатов выборов:

csv2json < 2017_results.csv | tr -d '\n' | ndjson-split > 2017_results.ndjson

Точно так же мы готовим нашу граничную геометрию как ndjson:

topo2geo const=- < uk-simpl-topo.json | \
ndjson-split 'd.features' > uk-simpl.ndjson

И объедините их вместе:

ndjson-join 'd.id' 'd.constituency_id' uk-simpl.ndjson 2017_results.ndjson \
| ndjson-map 'd[0].results = d[1], d[0]' \
> uk_2017_results.ndjson

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

Код ниже используетndjson-mapназначать каждому элементу (то есть избирательному округу) в GeoJson свойство, называемоеfill, Когда мы используемgeo2svgчтобы создать наше изображение, оно подберет это свойство и использует его для правильной окраски полигонов. Значениеfillдолжна быть строка, содержащая шестнадцатеричный код цвета, который должен быть у каждого многоугольника (например,#0087dc).

ndjson-map -r d3 'z = d3.scaleSequential(d3.interpolateViridis).domain([0.5, .8]), d.properties.fill = z(d.results["turnout "]), d' < uk_2017_results.ndjson \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > uk_2017_turnout.svg

Возможно, стоит попытаться сломать эту строчку, чтобы понять, что происходит. Мы используем d3 для перевода информации о явке избирателей (число от 0 до 1, представляющее долю избирателей, которые фактически проголосовали в этом избирательном округе) в шестнадцатеричный код.ndjson-mapИнструмент позволяет нам пройти через каждый избирательный округ и применить какую-то операцию. Аргумент, который мы передаемndjson-mapэто три строки Javascript.

Вторая строка:

d.properties.fill = z(d.results["turnout "])

говорит, что для каждой функции создайте свойство с именемfillзначение которого является выходом функцииz, Эта функцияzпринимает значение явки - которое мы уже прикрепили к функции в нашем предыдущем соединении - и возвращает шестнадцатеричный код, представляющий цвет. этоzФункция определена в первой строке Javascript:

z = d3.scaleSequential(d3.interpolateViridis).domain([0.5,0.8])

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

Мы можем открыть этоuk_2017_turnour.svgфайл в браузере:

Зеленая и приятная земля: явка на всеобщих выборах 2017 года в Великобритании

Легенда была создана отдельно вНаблюдаемая тетрадьи скопировать и вставить в файл SVG.

Адаптация подхода к победителям сюжета

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

Javascript, который я передаюndjson-mapЗдесь я чувствую себя немного неуклюже, поскольку я определяю объект, который содержит поиск цвета для каждой партии.

ndjson-map -r d3 's={"con_share":"#0087dc", "lib_share":"#FDBB30", "lab_share":"#d50000", "natSW_share":"#3F8428", "pld":"#3F8428", "oth_share":"#aaaaaa"}, u = ({ con_share, lab_share, lib_share, oth_share, natSW_share }) => ({ con_share, lab_share, lib_share, oth_share, natSW_share }),
d.properties.fill = s[Object.keys(u(d.results)).reduce((a, b) => d.results[a] > d.results[b] ? a : b)], d' < uk_2017_results.ndjson \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > uk_2017_winner.svg

Опять же, немного разбив этот JavaScript, первая строка:

s={"con_share":"#0087dc", "lib_share":"#FDBB30", "lab_share":"#d50000", "natSW_share":"#3F8428", "pld":"#3F8428", "oth_share":"#aaaaaa"}

просто определяет поиск от партии к цвету. Вторая строка:

u = ({ con_share, lab_share, lib_share, oth_share, natSW_share }) => ({ con_share, lab_share, lib_share, oth_share, natSW_share })

Этодеструктурирующее заданиекоторый мы используем для извлечения номеров голосов из объекта результатов выборов. Тогда последняя строка:

d.properties.fill = s[Object.keys(u(d.results)).reduce((a, b) => d.results[a] > d.results[b] ? a : b)]

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

Посмотрим на результат:

Всеобщие выборы в Великобритании в 2017 году раскрасились по победителям.

У нас есть наш результат. Вы можете остановиться здесь, если не хотите увидеть, как этого можно достичь с помощью стека Python.

Часть 2: Обработка данных в Python

В первой части этого руководства мы составили карту выборов в Великобритании, используя инструменты Javascript в командной строке, а такжеМайк Босток«sРуководство по картографии из командной строки, Для сбора данных я обычно тяготею к среде Python. Итак, во второй части я вернусь и посмотрю, как этого можно достичь с помощью Pandas и GeoPandas в Python REPL.

Чтобы оставаться ближе к стилю «командной строки», я собираюсь сделать это с помощью Python REPL, но, конечно, сессия Jupyter Notebook / Lab будет хорошей идеей.

python

Мы собираемся использоватьGeoPandasбиблиотека, а также панды для некоторых общих манипуляций с данными:

import pandas as pd
import geopandas as gpd

Сначала давайте прочитаем в границах полигонов округов Великобритании в GeoDataFrame:

gb_shape = gpd.read_file('westminster_const_region.shp')
Быстрый просмотр импортированного шейп-файла как DataFrame.

Если вы хотите проверить геометрию более наглядно, мы можем импортировать matplotlib, а затем просто вызвать.plot()на GeoDataFrame:

import matplotlib.pyplot as plt
gb_shape.plot(); plt.show()
Быстрая визуальная проверка содержимого шейп-файла GB.

Я заметил на более поздних графиках, что Шетландские острова отсутствовали на карте, я думаю, что есть ошибка вCODEполе в этих данных:S1400005должно бытьS14000051, Итак, давайте быстро изменим это сейчас

gb_shape['CODE']=gb_shape.CODE.apply(lambda s: 'S14000051' if s == 'S1400005' else s)

Теперь загрузите в округах Северной Ирландии:

ni_shape = gpd.read_file(‘OSNI_Open_Data_Largescale_Boundaries__Parliamentary_Constituencies_2008.shp’)

Мы хотим объединить два набора границ в один DataFrame. Для этого сначала нужно настроить имена столбцов так, чтобы они совпадали:

ni_shape.rename(columns={'PC_NAME':'NAME','PC_ID':'CODE'},inplace=True)

Во-вторых, как мы обнаружили в предыдущем уроке, геометрия границ GB и NI представлена ​​в разных системах координат. Данные граничного файла Великобритании (ГБ) уже спроецированы наБританская национальная сеть(EPSG: 27700). Однако данные геометрии в границах Северной Ирландии (NI) определены вEPSG: 4326, Вместо достиженияogr2ogrмы можем использовать GeoPandas, чтобы сделать это преобразование:

ni_shape.to_crs({‘init’:’epsg:27700'},inplace=True)

И теперь мы должны быть готовы соединить два набора геометрии, используяpd.concat, который должен доставить другойGeoDataFrame,

uk_boundaries = pd.concat([gb_shape[['NAME','CODE','geometry']],ni_shape[['NAME','CODE','geometry']]], sort=False)

Объединить результаты выборов с геометрией

Теперь прочитайте результаты наших выборов в DataFrame:

results=pd.read_csv(‘1918_2017election_results.csv’)
Первые 10 строк результатов выборов в CSV-файл.

Я хочу отфильтровать этот DataFrame, чтобы он включал только результаты выборов 2017 года. [Я также сначала переименовал некоторые столбцы, чтобы исправить концевые пробелы, и позже планировал объединить их с данными геометрии.],

# rename a few columns
results.rename(columns={
‘turnout ‘:’turnout’,
’constituency_id’:’CODE’,
},inplace=True)# keep only the 2017 election results
results=results[results.election=='2017']# keep only the columns we need
results=results[[‘CODE’,’constituency’,’country/region’,’con_share’,’lib_share’,’lab_share’,’natSW_share’,’oth_share’,’turnout’]]uk_results = uk_boundaries.merge(results, on='CODE')

uk_resultsDataFrame содержит геометрию и результаты выборов для каждого избирательного округа.

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

uk_results[‘geometry’] = uk_results.geometry.simplify(tolerance=500)

Подготовка к презентации

Хотя, конечно, возможно создать SVG с использованием стека Python, моя первая цель - создать выход GeoJSON из этих объединенных данных. Это позволило бы мне вернуться в мир JavaScriptКомандная строка Картография, илиObservableHQтетради для производства работ по финальной презентации.

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

Сначала я делаю небольшой словарь, определяющий цвета партии, взятые изВот:

party_colours={
“con”:”#0087dc”,
“lib”:”#FDBB30",
“lab”:”#d50000",
"snp":"#FFF95D",
“pld”:”#3F8428"
}

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

uk_results['winner']= uk_results[['con_share','lab_share','lib_share','natSW_share','oth_share']].fillna(0.).astype(float).idxmax(axis=1).apply(lambda s: s[:3])

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

def sub_nat(winner, region):
if winner=='nat':
if region=='Wales':
return 'pld'
else:
return 'snp'
else:
return winneruk_results['winner']=uk_results[['country/region','winner']].apply(lambda r: sub_nat(r[1],r[0]), axis=1)uk_results['winner_fill']=uk_results.winner.apply(lambda s: party_colours.get(s,”#aaaaaa”))

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

Теперь мы можем выписать это DataFrame ad GeoJSON:

uk_results.to_file(‘gdp_uk_results.json’,driver=’GeoJSON’)

На этом этапе мы можем вернуться к командной картографии и создать svg:

geoproject 'd3.geoIdentity().reflectY(true).fitSize([960, 960], d)' < gdp_uk_results.json \
| ndjson-split 'd.features' \
| ndjson-map -r d3 'd.properties.fill = d.properties.winner_fill, d' \
| geo2svg -n --stroke none -p 1 -w 960 -h 960 > gdp_uk_winner.svg
Оказанный SVG

Так что у нас это. Я использовал Python и Pandas для манипулирования данными гораздо чаще, чем инструменты командной строки, такие какawkJavascript. Так что неизбежно я обнаружил, что проще и более интуитивно манипулировать геометрией и результатами выборов вместе в этом уроке, который описан в части 1. Возможно, это всего лишь моя предубежденность в знакомстве.

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

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

Благодаря@mbostockза все его инструменты и сочинения, которые вдохновляют.

Оригинальная статья

Footer decor

© www.machinelearningmastery.ru | Ссылки на оригиналы и авторов сохранены. | map