【数据分析必学】一文精通mapclassify:专业空间数据分类技术与地图可视化实战指南
引言
在地理数据可视化领域,如何合理地将连续数据划分为离散区间,直接决定了地图的表现力和准确性。一张优秀的专题地图(如人口密度图、收入分布图)背后,往往蕴含着精心设计的数据分类方案。mapclassify作为Python生态中专门用于空间数据分类的库,提供了丰富的分类算法和工具,帮助地图制作者科学、合理地表达空间数据。本文将全面解析mapclassify的使用方法及实战技巧,帮助你掌握专业的空间数据分类能力。
1. 为什么需要mapclassify?
在制作专题地图时,我们通常需要将连续的数值数据(如人口密度、GDP、温度等)转换为有限的几个类别,并用不同的颜色或图案表示。这个过程被称为"数据分类"或"数据离散化"。
1.1 数据分类的重要性
- 简化复杂性:将连续数据简化为少数几个类别,使地图更易于理解
- 突出模式:合适的分类方法可以突显数据的空间模式和异常值
- 控制视觉效果:不同的分类方法会产生不同的视觉效果,影响读图者的理解
1.2 不当分类的风险
选择不恰当的分类方法可能会:
- 掩盖重要的数据特征
- 夸大不重要的模式
- 误导地图读者
- 无法准确反映空间现象的分布规律
1.3 环境配置
# 安装必要的库
!pip install mapclassify geopandas matplotlib contextily numpy pandas
# 导入库
import mapclassify as mc
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import contextily as ctx
from matplotlib.colors import LinearSegmentedColormap
2. mapclassify的核心分类方法
mapclassify提供了多种分类算法,每种算法适用于不同的数据分布和可视化目标。
2.1 等间距分类法 (Equal Interval)
将数据范围划分为大小相等的区间。
# 生成示例数据
np.random.seed(42)
data = np.random.gamma(3, 2, 100) # 偏斜分布的数据
# 应用等间距分类
equal_interval = mc.EqualInterval(data, k=5)
print("等间距分类边界:", equal_interval.bins)
print("每个类别的数据数量:", equal_interval.counts)
# 可视化分类结果
plt.figure(figsize=(10, 6))
plt.hist(data, bins=20, alpha=0.5, color='gray')
for edge in equal_interval.bins:
plt.axvline(edge, color='red', linestyle='--')
plt.title('等间距分类法 (Equal Interval)')
plt.xlabel('数值')
plt.ylabel('频数')
plt.show()
优点:
- 计算简单,容易理解
- 适合均匀分布的数据
- 不同地图之间可以直接比较
缺点:
- 对于偏斜分布的数据,某些类别可能包含很少或没有数据
- 可能无法很好地反映数据的分布特征
适用场景:
- 均匀分布的数据(如温度、海拔等)
- 需要在多个地图之间进行比较的情况
- 数据范围和意义已被广泛接受(如0-100%的比例数据)
2.2 分位数分类法 (Quantile)
确保每个类别包含相同数量的观测值。
# 应用分位数分类
quantile = mc.Quantiles(data, k=5)
print("分位数分类边界:", quantile.bins)
print("每个类别的数据数量:", quantile.counts)
# 可视化分类结果
plt.figure(figsize=(10, 6))
plt.hist(data, bins=20, alpha=0.5, color='gray')
for edge in quantile.bins:
plt.axvline(edge, color='green', linestyle='--')
plt.title('分位数分类法 (Quantile)')
plt.xlabel('数值')
plt.ylabel('频数')
plt.show()
优点:
- 每个类别包含相同数量的数据点,避免空类别
- 对异常值不敏感
- 适合不均匀分布的数据
缺点:
- 可能会将相似值分到不同类别中
- 不同地图之间难以直接比较
- 可能导致视觉上的误导
适用场景:
- 偏斜分布的数据
- 需要确保每个类别有足够样本的情况
- 相对排名分析(如前20%、中间40%、后40%等)
2.3 自然断点分类法 (Jenks Natural Breaks)
最小化每个类内的方差,最大化类间的方差。
# 应用自然断点分类
natural_breaks = mc.NaturalBreaks(data, k=5)
print("自然断点分类边界:", natural_breaks.bins)
print("每个类别的数据数量:", natural_breaks.counts)
# 可视化分类结果
plt.figure(figsize=(10, 6))
plt.hist(data, bins=20, alpha=0.5, color='gray')
for edge in natural_breaks.bins:
plt.axvline(edge, color='blue', linestyle='--')
plt.title('自然断点分类法 (Jenks Natural Breaks)')
plt.xlabel('数值')
plt.ylabel('频数')
plt.show()
优点:
- 最大限度地保留数据自然分组
- 边界位于数据的"断点"处,分类更合理
- 通常产生最佳的视觉效果
缺点:
- 计算复杂度高
- 每个数据集的分类都不同,难以在多个地图间比较
- 对异常值敏感
适用场景:
- 单一地图的优化表达
- 数据中存在明显的自然分组
- 探索性数据分析
2.4 标准差分类法 (Standard Deviation)
基于平均值和标准差来定义类别。
# 生成正态分布数据
np.random.seed(42)
normal_data = np.random.normal(100, 15, 200)
# 应用标准差分类
std_dev = mc.StdMean(normal_data)
print("标准差分类边界:", std_dev.bins)
print("每个类别的数据数量:", std_dev.counts)
# 可视化分类结果
plt.figure(figsize=(10, 6))
plt.hist(normal_data, bins=20, alpha=0.5, color='gray')
for edge in std_dev.bins:
plt.axvline(edge, color='purple', linestyle='--')
plt.title('标准差分类法 (Standard Deviation)')
plt.xlabel('数值')
plt.ylabel('频数')
plt.show()
优点:
- 突显与均值的偏离程度
- 适合正态分布的数据
- 统计学意义明确
缺点:
- 不适合偏斜分布
- 类别数量通常固定为6或7个
- 需要数据近似正态分布
适用场景:
- 分析与平均值的偏差(如温度异常、降水偏差)
- 地理统计学分析
- 满足正态分布假设的数据
2.5 几何间隔分类法 (Fisher-Jenks)
Fisher-Jenks算法的最优实现,适合大数据集。
# Fisher-Jenks分类(优化的自然断点)
fisher_jenks = mc.FisherJenks(data, k=5)
print("Fisher-Jenks分类边界:", fisher_jenks.bins)
print("每个类别的数据数量:", fisher_jenks.counts)
# 可视化分类结果
plt.figure(figsize=(10, 6))
plt.hist(data, bins=20, alpha=0.5, color='gray')
for edge in fisher_jenks.bins:
plt.axvline(edge, color='orange', linestyle='--')
plt.title('Fisher-Jenks分类法')
plt.xlabel('数值')
plt.ylabel('频数')
plt.show()
优点:
- 自然断点方法的高效实现
- 适合大型数据集
- 保留数据的自然分组
缺点:
- 与自然断点方法类似的局限性
- 计算开销仍然较大
适用场景:
- 大型空间数据集
- 需要优化计算性能的情况
2.6 其他分类方法
mapclassify还提供了许多其他分类方法:
# 头尾分割法(强调分布尾部)
headtail = mc.HeadTailBreaks(data)
print("头尾分割分类边界:", headtail.bins)
print("每个类别的数据数量:", headtail.counts)
# 最大间隔分类法
max_p = mc.MaximumBreaks(data, k=5)
print("最大间隔分类边界:", max_p.bins)
print("每个类别的数据数量:", max_p.counts)
# 用户自定义分类法
user_defined = mc.UserDefined(data, [5, 10, 15, 20])
print("用户自定义分类边界:", user_defined.bins)
print("每个类别的数据数量:", user_defined.counts)
3. 分类方法的比较与选择
不同的分类方法会产生不同的视觉效果。理解这些差异对于选择合适的分类方法至关重要。
3.1 多种方法的视觉对比
# 创建模拟数据集
np.random.seed(42)
data = np.concatenate([
np.random.normal(50, 10, 80), # 主体数据
np.random.normal(100, 5, 20) # 少量高值
])
# 应用多种分类方法
classifiers = {
"等间距分类": mc.EqualInterval(data, k=5),
"分位数分类": mc.Quantiles(data, k=5),
"自然断点分类": mc.NaturalBreaks(data, k=5),
"Fisher-Jenks分类": mc.FisherJenks(data, k=5)
}
# 可视化比较
fig, axes = plt.subplots(len(classifiers), 1, figsize=(12, 10), sharex=True)
plt.subplots_adjust(hspace=0.5)
for i, (name, classifier) in enumerate(classifiers.items()):
axes[i].hist(data, bins=30, alpha=0.5, color='gray')
for edge in classifier.bins:
axes[i].axvline(edge, color=['red', 'green', 'blue', 'orange'][i], linestyle='--')
axes[i].set_title(f'{name} (k=5)')
axes[i].set_ylabel('频数')
# 显示每个类别的数据量
for j, count in enumerate(classifier.counts):
if j < len(classifier.counts) - 1:
x_pos = (classifier.bins[j] + classifier.bins[j+1]) / 2
axes[i].text(x_pos, max(axes[i].get_ylim()) * 0.7, f'n={count}',
ha='center', va='center', backgroundcolor='white')
axes[-1].set_xlabel('数值')
plt.tight_layout()
plt.show()
3.2 如何选择合适的分类方法
选择分类方法时,应考虑以下因素:
-
数据分布特征:
- 均匀分布:等间距分类
- 正态分布:标准差分类
- 偏斜分布:分位数分类或自然断点
- 多峰分布:自然断点分类
-
可视化目标:
- 突显异常值:头尾分割法
- 展示整体分布趋势:等间距或分位数
- 识别数据的自然分组:自然断点或Fisher-Jenks
- 强调均值偏差:标准差分类
-
地图用途:
- 探索性分析:自然断点或Fisher-Jenks
- 多地图比较:等间距或分位数
- 时间序列地图:固定间隔或用户自定义
-
受众群体:
- 专业分析人员:可使用复杂的分类方法
- 普通公众:优先选择易于理解的分类方法
4. 实际应用案例
4.1 使用真实数据:美国县级人口密度图
# 加载美国县级数据
usa = gpd.read_file(gpd.datasets.get_path('usa_counties'))
# 计算人口密度
usa['pop_density'] = usa['POP2010'] / usa['ALAND'] * 1000000 # 每平方公里人口
# 筛选出本土48州数据,排除极端异常值
usa_lower48 = usa[(usa['STATEFP'] != '02') & (usa['STATEFP'] != '15')] # 排除阿拉斯加和夏威夷
usa_lower48 = usa_lower48[usa_lower48['pop_density'] < 5000] # 排除极端高值
# 应用不同分类方法
classifiers = {
"等间距分类": mc.EqualInterval(usa_lower48['pop_density'], k=5),
"分位数分类": mc.Quantiles(usa_lower48['pop_density'], k=5),
"自然断点分类": mc.NaturalBreaks(usa_lower48['pop_density'], k=5)
}
# 创建渐变色方案
colors = ['#ffffcc', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8']
cmap = LinearSegmentedColormap.from_list('custom_cmap', colors)
# 创建多子图比较
fig, axes = plt.subplots(1, 3, figsize=(18, 10))
plt.subplots_adjust(wspace=0.05)
for i, (name, classifier) in enumerate(classifiers.items()):
# 分类并映射到颜色
usa_lower48[f'class_{i}'] = classifier.yb
# 绘制地图
usa_lower48.plot(
column=f'class_{i}',
cmap=cmap,
ax=axes[i],
legend=True,
legend_kwds={'title': '人口密度\n(人/平方公里)', 'loc': 'lower right'}
)
axes[i].set_title(name, fontsize=14)
axes[i].set_axis_off()
axes[i].set_xlim([-125, -65])
axes[i].set_ylim([25, 50])
plt.suptitle('美国县级人口密度 - 不同分类方法比较', fontsize=18, y=0.95)
plt.tight_layout()
plt.show()
# 输出分类边界值
for name, classifier in classifiers.items():
print(f"{name}边界值: {classifier.bins.round(1).tolist()}")
4.2 中国省级GDP地图
# 模拟中国省级GDP数据
provinces = gpd.read_file('china_provinces.geojson') # 假设有这个文件
provinces['GDP'] = [91, 68, 44, 24, 39, 55, 77, 102, 42, 36, 27,
58, 47, 62, 38, 49, 65, 29, 41, 67, 88,
32, 72, 53, 44, 31, 48, 59, 39, 51, 47] # 示例数据
# 使用自然断点分类
natural_breaks = mc.NaturalBreaks(provinces['GDP'], k=5)
provinces['GDP_class'] = natural_breaks.yb
# 创建地图
fig, ax = plt.subplots(1, 1, figsize=(12, 10))
provinces.plot(
column='GDP_class',
cmap='OrRd',
linewidth=0.5,
ax=ax,
edgecolor='black',
legend=True,
legend_kwds={'title': 'GDP分类\n(万亿元)'}
)
# 添加省名标签
for idx, row in provinces.iterrows():
ax.annotate(row['name'], xy=(row.geometry.centroid.x, row.geometry.centroid.y),
ha='center', va='center', fontsize=8)
ax.set_title('中国省级GDP分布图 (自然断点分类)', fontsize=15)
ax.set_axis_off()
plt.tight_layout()
plt.show()
5. 与其他地理库集成
5.1 与GeoPandas集成
mapclassify与GeoPandas无缝集成,可以直接在GeoPandas的plot函数中使用:
# 使用mapclassify自动执行分类
usa_lower48.plot(
column='pop_density',
scheme='NaturalBreaks', # 使用自然断点分类
k=5, # 分类数量
cmap='Blues', # 色彩方案
legend=True,
figsize=(12, 8)
)
plt.title('美国县级人口密度 (自然断点分类)', fontsize=15)
plt.axis('off')
plt.show()
# 使用分位数分类绘制人口图
usa_lower48.plot(
column='pop_density',
scheme='Quantiles', # 使用分位数分类
k=5, # 分类数量
cmap='YlOrRd', # 色彩方案
legend=True,
figsize=(12, 8)
)
plt.title('美国县级人口密度 (分位数分类)', fontsize=15)
plt.axis('off')
plt.show()
5.2 与matplotlib和contextily集成
# 使用自定义分类方法并添加底图
fig, ax = plt.subplots(1, 1, figsize=(12, 10))
# 选择东部地区绘制更详细的地图
eastern_states = usa_lower48[usa_lower48.geometry.centroid.x > -90]
# 应用Fisher-Jenks分类
fj = mc.FisherJenks(eastern_states['pop_density'], k=6)
eastern_states['density_class'] = fj.yb
# 绘制地图
eastern_states.to_crs(epsg=3857).plot(
column='density_class',
categorical=True,
cmap='viridis',
linewidth=0.5,
edgecolor='black',
alpha=0.7,
ax=ax
)
# 添加底图
ctx.add_basemap(ax)
# 添加标题和说明
plt.title('美国东部地区人口密度 (Fisher-Jenks分类)', fontsize=15)
plt.axis('off')
# 添加自定义图例
import matplotlib.patches as mpatches
# 获取分类边界
bounds = np.round(fj.bins, 1)
bounds = np.insert(bounds, 0, np.round(eastern_states['pop_density'].min(), 1))
# 创建图例
patches = []
for i in range(len(bounds)-1):
label = f'{bounds[i]} - {bounds[i+1]}'
color = plt.cm.viridis(i/len(bounds))
patches.append(mpatches.Patch(color=color, label=label))
plt.legend(handles=patches, title='人口密度\n(人/平方公里)',
loc='upper left', bbox_to_anchor=(1, 1))
plt.tight_layout()
plt.show()
6. 高级技巧与最佳实践
6.1 处理偏斜数据
对于高度偏斜的数据,常规分类方法可能效果不佳:
# 创建高度偏斜的数据
np.random.seed(42)
skewed_data = np.random.lognormal(0, 1, 1000)
# 原始数据的分类效果
classifiers = {
"等间距分类": mc.EqualInterval(skewed_data, k=5),
"分位数分类": mc.Quantiles(skewed_data, k=5),
"自然断点分类": mc.NaturalBreaks(skewed_data, k=5),
"头尾分割分类": mc.HeadTailBreaks(skewed_data)
}
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for i, (name, classifier) in enumerate(classifiers.items()):
axes[i].hist(skewed_data, bins=50, alpha=0.5, color='gray')
for edge in classifier.bins:
axes[i].axvline(edge, color=['red', 'green', 'blue', 'purple'][i], linestyle='--')
axes[i].set_title(name)
axes[i].set_yscale('log') # 使用对数尺度以便更好地查看分布
axes[i].set_xlim([0, 20]) # 限制x轴范围以便更好地查看主体分布
plt.tight_layout()
plt.show()
# 对数变换后再分类
log_data = np.log1p(skewed_data) # log(1+x)变换
log_classifiers = {
"对数变换后等间距分类": mc.EqualInterval(log_data, k=5),
"对数变换后分位数分类": mc.Quantiles(log_data, k=5),
"对数变换后自然断点分类": mc.NaturalBreaks(log_data, k=5)
}
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for i, (name, classifier) in enumerate(log_classifiers.items()):
axes[i].hist(log_data, bins=50, alpha=0.5, color='gray')
for edge in classifier.bins:
axes[i].axvline(edge, color=['red', 'green', 'blue'][i], linestyle='--')
axes[i].set_title(name)
plt.tight_layout()
plt.show()
# 将对数空间中的分类边界转换回原始空间
for name, classifier in log_classifiers.items():
original_bins = np.expm1(classifier.bins) # 逆变换 exp(x)-1
print(f"{name}在原始空间中的边界值: {original_bins.round(2)}")
6.2 使用GOF(拟合优度)评估分类质量
mapclassify提供了GOF指标来评估分类方法的质量:
# 比较不同分类方法的GOF
np.random.seed(42)
test_data = np.random.gamma(2, 3, 200) # 使用gamma分布作为测试数据
methods = {
"等间距分类": mc.EqualInterval,
"分位数分类": mc.Quantiles,
"自然断点分类": mc.NaturalBreaks,
"Fisher-Jenks分类": mc.FisherJenks
}
k_values = range(3, 10) # 测试不同的k值
results = {}
for method_name, method_class in methods.items():
gof_values = []
for k in k_values:
classifier = method_class(test_data, k=k)
gof_values.append(classifier.goodness_of_fit())
results[method_name] = gof_values
# 绘制GOF比较图
plt.figure(figsize=(10, 6))
for method_name, gof_values in results.items():
plt.plot(k_values, gof_values, marker='o', label=method_name)
plt.xlabel('类别数量 (k)')
plt.ylabel('拟合优度 (GOF)')
plt.title('不同分类方法的拟合优度比较')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.tight_layout()
plt.show()
6.3 制作专业级地图图例
# 使用自然断点分类并创建专业图例
data = np.random.gamma(2, 3, 100)
classifier = mc.NaturalBreaks(data, k=5)
# 模拟地图数据
np.random.seed(42)
x = np.random.rand(100)
y = np.random.rand(100)
values = data
fig, ax = plt.subplots(figsize=(10, 8))
# 为每个类别分配颜色
cmap = plt.cm.YlOrRd
boundaries = list(classifier.bins)
boundaries.insert(0, min(data))
norm = plt.Normalize(min(data), max(data))
# 绘制散点图,按类别着色
for i in range(len(boundaries)-1):
mask = (values >= boundaries[i]) & (values <= boundaries[i+1])
if i == len(boundaries)-2: # 最后一个类别包含上边界
mask = (values >= boundaries[i])
ax.scatter(x[mask], y[mask], c=[cmap(norm(np.mean([boundaries[i], boundaries[i+1]]))],
label=f'{boundaries[i]:.1f} - {boundaries[i+1]:.1f}')
# 创建图例
ax.legend(title='数值分类', fontsize=8, title_fontsize=10,
bbox_to_anchor=(1.05, 1), loc='upper left')
# 添加标题和轴标签
ax.set_title('自然断点分类示例', fontsize=15)
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.grid(True, linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()
6.4 分类方法的自动选择
# 根据数据特征自动选择分类方法
def auto_classifier(data, k=5):
"""根据数据特征自动选择合适的分类方法"""
# 计算数据的偏度
from scipy import stats
skewness = stats.skew(data)
# 检查是否接近正态分布
normality = stats.shapiro(data)[1] # p值
# 检查是否有极端异常值
q1, q3 = np.percentile(data, [25, 75])
iqr = q3 - q1
has_outliers = np.any((data < q1 - 1.5 * iqr) | (data > q3 + 1.5 * iqr))
# 基于数据特征选择分类方法
if normality > 0.05: # 接近正态分布
return mc.StdMean(data), "标准差分类 (正态分布)"
elif abs(skewness) > 2 or has_outliers: # 高度偏斜或有异常值
if abs(skewness) > 4: # 极端偏斜
return mc.HeadTailBreaks(data), "头尾分割分类 (极端偏斜)"
else:
return mc.Quantiles(data, k=k), "分位数分类 (偏斜分布)"
else: # 中等偏斜
return mc.NaturalBreaks(data, k=k), "自然断点分类 (默认选择)"
# 测试不同分布的数据
distributions = {
"正态分布": np.random.normal(100, 15, 200),
"均匀分布": np.random.uniform(0, 100, 200),
"偏斜分布": np.random.gamma(2, 10, 200),
"极端偏斜": np.random.lognormal(0, 1, 200),
"双峰分布": np.concatenate([np.random.normal(30, 5, 100), np.random.normal(70, 5, 100)])
}
for name, data in distributions.items():
classifier, method_name = auto_classifier(data)
print(f"{name}: 自动选择 {method_name}")
print(f" 分类边界: {classifier.bins.round(1)}")
print(f" 各类别数据量: {classifier.counts}")
print()
7. 综合实战案例:城市社会经济分析
7.1 多变量分类与地图可视化
# 假设有城市街区数据
# 创建模拟数据
np.random.seed(42)
n = 100 # 街区数量
# 创建网格状的几何形状
from shapely.geometry import Polygon
geometries = []
for i in range(10):
for j in range(10):
# 创建网格单元作为街区
geometries.append(Polygon([(i, j), (i+1, j), (i+1, j+1), (i, j+1)]))
# 创建社会经济属性
data = {
'income': np.random.lognormal(10, 0.5, n), # 收入水平
'education': np.random.normal(15, 3, n), # 教育年限
'housing': np.random.gamma(20, 1.5, n), # 房价指数
'age': np.random.normal(40, 10, n) # 平均年龄
}
# 创建GeoDataFrame
blocks = gpd.GeoDataFrame(data, geometry=geometries)
# 对每个变量应用自然断点分类
for col in ['income', 'education', 'housing', 'age']:
classifier = mc.NaturalBreaks(blocks[col], k=5)
blocks[f'{col}_class'] = classifier.yb
# 创建多变量可视化
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
axes = axes.flatten()
variables = ['income', 'education', 'housing', 'age']
cmaps = ['Reds', 'Blues', 'Greens', 'Purples']
titles = ['收入水平', '教育年限', '房价指数', '平均年龄']
for i, (var, cmap, title) in enumerate(zip(variables, cmaps, titles)):
blocks.plot(
column=f'{var}_class',
cmap=cmap,
linewidth=0.5,
edgecolor='black',
ax=axes[i],
legend=True,
legend_kwds={'title': title}
)
axes[i].set_title(title)
axes[i].set_axis_off()
plt.suptitle('城市街区社会经济特征分析', fontsize=16, y=0.98)
plt.tight_layout()
plt.subplots_adjust(top=0.9)
plt.show()
7.2 创建复合指数并分类
# 创建社会经济综合指数
# 标准化变量
for col in ['income', 'education', 'housing', 'age']:
blocks[f'{col}_std'] = (blocks[col] - blocks[col].mean()) / blocks[col].std()
# 创建综合指数 (示例: 收入和教育正向影响,年龄负向影响)
blocks['ses_index'] = (blocks['income_std'] * 0.4 +
blocks['education_std'] * 0.4 -
blocks['age_std'] * 0.2)
# 应用Fisher-Jenks分类
classifier = mc.FisherJenks(blocks['ses_index'], k=5)
blocks['ses_class'] = classifier.yb
# 绘制综合指数地图
fig, ax = plt.subplots(figsize=(10, 10))
blocks.plot(
column='ses_class',
cmap='RdYlBu_r', # 红(低)到蓝(高)
linewidth=0.5,
edgecolor='black',
ax=ax,
legend=True,
legend_kwds={'title': '社会经济\n综合指数'}
)
# 添加标题和注释
ax.set_title('城市街区社会经济综合指数分布', fontsize=15)
ax.set_axis_off()
# 添加说明文本
plt.figtext(0.15, 0.05, '注: 综合指数由收入(40%)、教育(40%)和年龄(-20%)加权合成',
wrap=True, horizontalalignment='left', fontsize=10)
plt.tight_layout()
plt.show()
8. 总结与最佳实践
8.1 不同分类方法的适用场景总结
分类方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
等间距分类 | 均匀分布数据;多地图比较 | 简单易懂;便于比较 | 对偏斜数据效果差 |
分位数分类 | 偏斜分布;确保每类样本量相等 | 无空类别;稳定 | 可能合并不同特征;难以比较 |
自然断点分类 | 存在自然分组;单地图展示 | 反映数据结构;视觉效果好 | 计算复杂;难以比较 |
标准差分类 | 正态分布;偏差分析 | 统计意义明确 | 不适合非正态分布 |
Fisher-Jenks | 大数据集;存在自然分组 | 优化的断点算法;效果好 | 计算开销大 |
头尾分割法 | 极端偏斜;长尾分布 | 突显高值 | 类别数不固定 |
用户自定义 | 特定阈值;政策分析 | 完全可控 | 需要领域知识 |
8.2 制作高质量专题地图的建议
-
了解你的数据:
- 检查数据分布(直方图、箱线图)
- 识别异常值和极端值
- 了解数据的自然分组特征
-
选择合适的分类方法:
- 基于数据特征和可视化目标
- 尝试多种方法并比较
- 对偏斜数据考虑预处理(如对数变换)
-
慎重选择类别数量:
- 通常3-7个类别最为合适
- 使用GOF指标辅助决策
- 考虑观众接受能力
-
注意色彩选择:
- 使用顺序色彩方案表示连续数据
- 确保色彩可分辨(包括对色盲友好)
- 与数据变化方向一致(如深色通常表示高值)
-
提供清晰的图例:
- 包含分类边界值
- 添加单位和说明
- 考虑使用易于理解的标签而非纯数字
-
考虑地图用途:
- 探索性分析可以突出模式
- 公众传播需要简洁明了
- 多地图比较需保持一致的分类方法
8.3 进阶学习方向
掌握了mapclassify的基础用法后,可以考虑以下进阶学习方向:
- 空间自相关分析:使用GeoDa、PySAL等工具分析空间模式
- 时空数据分类:处理时间序列数据的分类技术
- 多变量分类方法:如k-means聚类和主成分分析
- 交互式地图可视化:使用Folium、Dash等创建交互地图
- 地理加权回归:考虑空间位置的回归分析方法
参考资源
- mapclassify官方文档: https://pysal.org/mapclassify/
- GeoPandas官方文档: https://geopandas.org/
- 《GIS Cartography: A Guide to Effective Map Design》(Gretchen N. Peterson)
- 《Choropleth Maps Through the Years》(Cynthia Brewer)
- 《How to Lie with Maps》(Mark Monmonier)
通过本文的学习,你已经掌握了使用mapclassify进行空间数据分类的核心技术。合理的数据分类不仅能提升地图的视觉效果,更能准确传达空间数据的内在规律,是地理数据分析和可视化中不可或缺的技能。希望这些知识能够帮助你创建更加专业、精确的专题地图!