Питон: как создать картограмму из шейп-файла из Канады?
моя цель здесь-создать картограмма Канады в Python. Предположим, у меня есть словарь со значениями, относящимися к каждой канадской провинции / территории:
myvalues={'Alberta': 1.0,
'British Columbia': 2.0,
'Manitoba': 3.0,
'New Brunswick': 4.0,
'Newfoundland and Labrador': 5.0,
'Northwest Territories': 6.0,
'Nova Scotia': 7.0,
'Nunavut': 8.0,
'Ontario': 9.0,
'Prince Edward Island': 10.0,
'Quebec': 11.0,
'Saskatchewan': 12.0,
'Yukon': 13.0}
теперь я хочу покрасить каждую провинцию на основе соответствующего значения в myvalues
, используя непрерывную цветовую карту (например, оттенки красного). как это сделать?
до сих пор мне удалось построить только канадские провинции / территории в пределах matplotlib, но их формы появляются в уникальном цвете, и я не знаю, как изменить это в соответствии с числами в myvalues
(возможно, мне нужно поиграть с patches
но я не знаю как).
здесь вы можете найти шейп-файл:http://www.filedropper.com/canadm1_1
и это мой код на сегодняшний день:
import shapefile
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
# -- input --
sf = shapefile.Reader("myfolderCAN_adm1.shp")
recs = sf.records()
shapes = sf.shapes()
Nshp = len(shapes)
cns = []
for nshp in xrange(Nshp):
cns.append(recs[nshp][1])
cns = array(cns)
cm = get_cmap('Dark2')
cccol = cm(1.*arange(Nshp)/Nshp)
# -- plot --
fig = plt.figure()
ax = fig.add_subplot(111)
for nshp in xrange(Nshp):
ptchs = []
pts = array(shapes[nshp].points)
prt = shapes[nshp].parts
par = list(prt) + [pts.shape[0]]
for pij in xrange(len(prt)):
ptchs.append(Polygon(pts[par[pij]:par[pij+1]]))
ax.add_collection(PatchCollection(ptchs,facecolor=None,edgecolor='k', linewidths=.5))
ax.set_xlim(-160,-40)
ax.set_ylim(40,90)
это изображение, которое я получаю так далеко:
редактировать
я понимаю, решение должно быть в следующих строках:
cm = get_cmap('OrRd')
cccol = cm(1.*arange(Nshp)/Nshp)
приведенный выше скрипт создает cccol
массив, который на самом деле имеет такую форму:
array([[ 1. , 0.96862745, 0.9254902 , 1. ],
[ 0.99766244, 0.93356402, 0.84133796, 1. ],
[ 0.99520185, 0.89227221, 0.74749713, 1. ],
[ 0.99274125, 0.84306037, 0.64415227, 1. ],
[ 0.99215686, 0.78754327, 0.5740254 , 1. ],
[ 0.99186467, 0.71989237, 0.50508269, 1. ],
[ 0.98940408, 0.60670514, 0.39927722, 1. ],
[ 0.97304114, 0.50618995, 0.32915034, 1. ],
[ 0.94105344, 0.40776625, 0.28732027, 1. ],
[ 0.88521339, 0.28115341, 0.19344868, 1. ],
[ 0.8220992 , 0.16018455, 0.10345252, 1. ],
[ 0.73351789, 0.04207613, 0.02717416, 1. ],
[ 0.61959248, 0. , 0. , 1. ]])
я не знаю, почему он имеет 4 столбца, но я полагаю, что если я могу каким-то образом связать значения этого массива с указанными в values
дикт, я могу решить проблему. Любой идеи?
Изменить 2
я понял, что "трюк" находится в cccol = cm()
. Для того, чтобы связать это с провинциями, я попытался назначить
cccol = cm(myvalues.values(i) for i in myvalues.keys())
так что (на мой взгляд, по крайней мере) каждый цвет назначается на основе соответствующего ключа, и нет никаких ошибок. Проблема в том, что я получаю ошибку:
TypeError: Cannot cast array data from dtype('O') to dtype('int32') according to the rule 'safe'
.
как обойти это?
3 ответов
Это непосредственно не отвечает на ваш вопрос, но, надеюсь, решает вашу проблему так же. Вы смотрели на GeoPandas? Он предоставляет простой API для работы с шейп-файлами и построения графиков. Вы можете повторить ваш код, в том числе подготовке хороплет, всего несколько строк:
import geopandas as gpd
canada = gpd.read_file('CAN_adm1.shp')
canada.plot('myvalues', cmap='OrRd')
в этом примере предполагается, что шейп-файл имеет атрибут для каждой области, содержащий значения, которые вы хотите построить, и атрибут называется "myvalues". Если значения не хранится в шейп-файле, вы можете использовать canada.merge
объединения values
карта на GeoDataframe.
одно предостережение: в это время у GeoPandas нет простого способа построить легенду для цветов choropleth. (обнаруженная здесь)
запрос: пожалуйста, переименуйте ваш values
словарь к чему-то еще. Это имя сделало написание этого ответа намного более трудным. :)
не проверяли это, но попробуйте:
color_numbers = values.values()
# assumes the provinces are listed in the same order in values as
# they are in the shape file
for nshp in xrange(Nshp):
ptchs = []
# ... code omitted ...
the_facecolor = [(color_numbers[nshp]-1)/(Nshp-1), 0, 0]; #1..13 -> 0..1, then add G=B=0.
# change the computation if the values in the values dictionary are no longer 1..13
ax.add_collection(PatchCollection(ptchs, facecolor=the_facecolor, edgecolor='k', linewidths=.5))
выход, который вы получаете, имеет все синие пятна или [0,0,1]
. Так как эта строка не в cccol
, Я не думаю, что cccol
- это проблема. Кроме того, код, который вы добавили, никогда не ссылается cccol
после его создания! (Пожалуйста, добавьте ссылку на образец кода, с которого вы начали! :) )
в любом случае, задание facecolor
должно помочь, насколько я знаю. Преобразование values
вход в диапазон 0..1, затем делая [R,G,B] цветные записи, должны дать вам оттенки красного.
вы упомянули путаницу о cccol
быть списком списков. Это список кортежей RGBA (красный, зеленый, синий, Альфа-прозрачность). Они представляют собой 13 "равномерно расположенных" цветов от оранжевого до красного.
в вашем случае вы не хотите одинаково расположенных цветов, но цвета, соответствующие myvalues
. Этого:
cmap = matplotlib.cm.get_cmap('OrRd')
norm = matplotlib.colors.Normalize(min(myvalues.values()), max(myvalues.values()))
color_producer = matplotlib.cm.ScalarMappable(norm=norm, cmap=cmap)
теперь color_producer
метод to_rgba
, который принимает значения из myvalues
и преобразует их в правильные цвета. The Normalize
установка min и Max диапазона из myvalues
к крайним цветам красно-оранжевой цветовой карты.
теперь, когда вы создаете каждой провинции PatchCollection
, вы можете установить его facecolor
в кортеж RGBA, возвращенный color_producer
:
# Change the province name passed as you iterate through provinces.
rgba = color_producer.to_rgba(myvalues['Manitoba'])
PatchCollection(ptchs, facecolor=rgba, edgecolor='k', linewidths=.5)