python计算莫兰指数(Moran's I)并绘制地区热力图——以中国各省pm2.5为例

程序简述

计算省的pm2.5平均值作为观测矩阵,省会的距离的倒数作为空间权重矩阵,计算全局莫兰指数为0.49,显著性检验p值为3.75>1.96,得出中国地区的pm2.5存在空间正相关
程序输入:中国各地区pm2.5值
程序输出:莫兰散点图、莫兰指数、莫兰检验数、地区热力图

莫兰指数(Moran's I)是空间自相关系数的一种,其值分布在[-1,1],用于判别空间是否存在自相关。比如:一个小区内有钱人会集聚住在豪宅别墅区,平民们(比如我- -)则会集聚住在普通的楼房区,这里的个人财富就是一种观测值,呈现高高聚集,低低聚集的现象,

数据集截图

图1,这是部分程序计算的各省的平均pm2.5,作为观测值矩阵

图2,这是部分市的pm2.5,需要经过算术平均得到图1

程序/数据集下载

点击进入下载地址

核心代码解析

Module/MoranI.py(莫兰指数接口,可直接运行)

这部分的程序输入是空间权重矩阵、观测值矩阵,输出是全局和局部莫兰指数、全局和局部显著性检验,程序内部会将空间权重矩阵进行归一化处理

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import os

def moranI(W,X):
    '''
    W:空间权重矩阵
    X:观测值矩阵
    归一化空间权重矩阵后进行moran检验,实例https://bbs.pinggu.org/thread-3568074-1-1.html
    '''
    W = np.array(W)
    X = np.array(X)
    X = X.reshape(1,-1)
    W = W/W.sum(axis=1)#归一化
    n = W.shape[0]#空间单元数
    Z = X - X.mean()#离差阵
    S0 = W.sum()
    S1 = 0
    for i in range(n):
        for j in range(n):
            S1 += 0.5*(W[i,j]+W[j,i])**2
    S2 = 0
    for i in range(n):
        S2 += (W[i,:].sum()+W[:,i].sum())**2
    #全局moran指数
    I = np.dot(Z,W)
    I = np.dot(I,Z.T)
    I = n/S0*I/np.dot(Z,Z.T)
    #在正太分布假设下的检验数
    EI_N = -1/(n-1)
    VARI_N = (n**2*S1-n*S2+3*S0**2)/(S0**2*(n**2-1))-EI_N**2
    ZI_N = (I-EI_N)/(VARI_N**0.5)
    #在随机分布假设下检验数
    EI_R = -1/(n-1)
    b2 = 0
    for i in range(n):
        b2 += n*Z[0,i]**4
    b2 = b2/((Z*Z).sum()**2)
    VARI_R = n*((n**2-3*n+3)*S1-n*S2+3*S0**2)-b2*((n**2-n)*S1-2*n*S2+6*S0**2)
    VARI_R = VARI_R/(S0**2*(n-1)*(n-2)*(n-3))-EI_R**2
    ZI_R = (I-EI_R)/(VARI_R**0.5)
    #计算局部moran指数
    Ii = list()
    for i in range(n):
        Ii_ = n*Z[0,i]
        Ii__ = 0
        for j in range(n):
            Ii__ += W[i,j]*Z[0,j]
        Ii_ = Ii_*Ii__/((Z*Z).sum())
        Ii.append(Ii_)
    Ii = np.array(Ii)
    #局部检验数
    ZIi = list()
    EIi = Ii.mean()
    VARIi = Ii.var()
    for i in range(n):
        ZIi_ = (Ii[i]-EIi)/(VARIi**0.5)
        ZIi.append(ZIi_)
    ZIi = np.array(ZIi)
    #moran散点图
    #用来正常显示中文标签
    plt.rcParams['font.sans-serif']=['SimHei'] 
    #用来正常显示负号
    plt.rcParams['axes.unicode_minus']=False 
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)
    ax.spines['top'].set_color('none')
    ax.spines['right'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data', 0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data', 0))
    WZ = np.dot(Z,W)
    ax.scatter(Z,WZ,c='k')
    x1 = range(int(Z.min()),int(Z.max()+1))
    y1 = range(int(Z.min()),int(Z.max()+1))
    ax.plot(x1,y1,'k--',label='x=y')
    x2 = list(range(int(Z.min()),int(Z.max()+1)))
    y2 = np.array(x2)*I[0][0]
    ax.plot(x2,y2,'k-',label='I*x=y')
    ax.legend(loc='upper right')
    imgPath = os.path.join(os.getcwd(),'莫兰散点图.png')
    ax.set_title('莫兰散点图')
    fig.savefig(imgPath)
    return {
            'I':{'value':I[0,0],'desc':'全局moran指数'},
            'ZI_N':{'value':ZI_N[0,0],'desc':'正太分布假设下检验数'},
            'ZI_R':{'value':ZI_R[0,0],'desc':'随机分布假设下检验数'},
            'Ii':{'value':Ii,'desc':'局部moran指数'},
            'ZIi':{'value':ZIi,'desc':'局部检验数'},
            'img':{'path':imgPath,'desc':'莫兰散点图路径'}
            }
    

if __name__ == "__main__":
    w = [
         [0,1,1,0,0],
         [1,0,1,1,0],
         [1,1,0,1,0],
         [0,1,1,0,1],
         [0,0,0,1,0]
        ]
    w = np.array(w)
    x = [
         [8,6,6,3,2]
         ]
    x = np.array(x)
    print(moranI(w,x))

Module/CalPosition.py(计算两地距离接口)

这部分是计算空间权重矩阵的接口,但是需要申请百度地图API(或者直接戳下载该项目),该接口的详述可点击python调用百度地图API得到两地经纬度计算直线距离

# -*- coding: utf-8 -*-
from geopy.distance import geodesic
import urllib.parse
import hashlib
import requests
#这部分需要申请百度地图API
ak = ""#百度地图ak码
sk = ""#百度地图sk码

def getCoordinate(address):
    '''
    输入地址输出坐标(经度,维度)
    address:城市名
    '''
    #产生sn码
    queryStr = "/geocoder/v2/?address="+address+'&output=json&ak='+ak
    encodedStr = urllib.parse.quote(queryStr, safe="/:=&?#+!$,;'@()*[]")
    rawStr = encodedStr+sk
    sn = (hashlib.md5(urllib.parse.quote_plus(rawStr).encode("utf8")).hexdigest())
    #生成url      
    url = urllib.parse.quote("http://api.map.baidu.com"+queryStr+"&sn="+sn,safe="/:=&?#+!$,;'@()*[]")
    result = requests.get(url).json()
    coordinate = (result['result']['location']['lng'],result['result']['location']['lat'])
    return coordinate

def calDistance(place1,place2):
    '''
    输入两个地点名,输出直线距离(米)
    place1:地点1
    place2:地点2
    '''
    coor1 = getCoordinate(place1)#经纬度1
    coor2 = getCoordinate(place2)#经纬度2
    #这里输入纬度在前,经度在后,所以做一下反转
    distance = geodesic(coor1[::-1],coor2[::-1]).m#距离(米)
    return distance
    
if __name__ == "__main__":
    p1 = "北京市"
    r1 = getCoordinate(p1)
    print("%s经纬度"%p1,r1)
    p2 = "重庆市"
    r2 = getCoordinate(p2)
    print("%s经纬度"%p2,r2)
    distance = calDistance(p1,p2)
    print("两地距离约为%d米"%distance)

接口调用、运行效果

Main.py

这部分测试接口,将中国各市的pm2.5算术平均作省pm2.5为观测值矩阵,然后将各省名代入计算距离的接口得到空间距离矩阵,将空间距离矩阵的负3次幂作为空间权重矩阵,最后将观测值矩阵和空间权重矩阵输入莫兰指数接口,得到结论,中国各地区的pm2.5值存在空间正相关性

# -*- coding: utf-8 -*-
from Module.CalPosition import calDistance
from Module.MoranI import moranI
import pandas as pd
import numpy as np
import json
import os
from pyecharts import Map
import re

#路径目录
baseDir = os.path.dirname(os.path.abspath(__file__))#当前目录
staticDir = os.path.join(baseDir,'Static')#静态文件目录
resultDir = os.path.join(baseDir,'Result')#结果文件目录

#读取省市字典
with open(staticDir+'/中国省市字典.json','r') as f:    
    china = json.loads(f.read())
#省-pm2.5字典
provincePm = {'province':[],'pm2.5':[]}
#读取各市pm2.5,求平均值作为整个省的pm2.5值
for province in china:
    pm = []#该省所有市的pm2.5列表
    for city in china[province]:
        #文件名没有市这个字
        city = city.replace('市','')
        path = staticDir+'/中国各市2个月的pm2.5/%s.csv'%city
        if not os.path.exists(path):
            continue
        cityPm = pd.read_csv(path,engine="python")['pm25']
        pm.extend(cityPm.values.tolist())
    #列表不为空才添加
    if pm:
        provincePm['province'].append(province)
        provincePm['pm2.5'].append(np.mean(pm))
provincePm = pd.DataFrame(provincePm).set_index(['province'])
#构建空间权重矩阵
if not os.path.exists(resultDir+'/距离矩阵.xlsx'):
    spaceMatrix = pd.DataFrame({},index=provincePm.index,columns=provincePm.index)
    for province1 in spaceMatrix.index:
        for province2 in spaceMatrix.columns:
            if not np.isnan(spaceMatrix.loc[province1,province2]):
                continue
            #两地距离
            try:
                distance = calDistance(province1,province2)
                spaceMatrix.loc[province1,province2] = distance
                spaceMatrix.loc[province2,province1] = distance
            except:
                spaceMatrix.loc[province1,province2] = np.nan
                spaceMatrix.loc[province2,province1] = np.nan
                continue
            #地点相同,距离取无穷大,不然后面的倒数会报错
            if not distance:
                spaceMatrix.loc[province1,province2] = 1e+10
    #删除空行和列
    spaceMatrix = spaceMatrix.drop(['西藏藏族自治区','海南省'],axis=0)
    spaceMatrix = spaceMatrix.drop(['西藏藏族自治区','海南省'],axis=1)
    spaceMatrix.to_excel(resultDir+'/距离矩阵.xlsx')
else:
    spaceMatrix = pd.read_excel(resultDir+'/距离矩阵.xlsx',index_col=0)
#取反距离,次幂可去1~3
spaceMatrix = spaceMatrix**-3
#观测值矩阵
provincePm = provincePm.drop(['西藏藏族自治区','海南省'],axis=0)
#计算莫兰指数
result = moranI(spaceMatrix,provincePm)
print('全局莫兰指数为',np.round(result['I']['value'],2),'说明地区之间的pm2.5正相关')
print('全局莫兰检验数为',np.round(result['ZI_N']['value'],2),'>1.96,在0.05的水平上显著,认为pm2.5存在空间相关性')
#热力图
provice = list(re.sub(r'市|省|.族自治区|维吾尔自治区|自治区','',index) for index in spaceMatrix.index)
values = list(result['Ii']['value'])
map = Map("pm2.5莫兰指数热力图", '', width=1200, height=600)
map.add("", provice, values, visual_range=[-1, 1], maptype='china', is_visualmap=True,
        visual_text_color='#000')
map.render(path=resultDir+"/pm2.5莫兰指数热力图.html")

图3,全国各地的局部莫兰指数热力图,可以看出,大部分地区为暖色,即正相关

图4,结论

图5,莫兰散点图,横纵坐标一般是进行分析的数据的离差值,moran散点图经常用来研究局部空间不稳定性,其四个象限分别对应于区域单元与其邻居之间四种类型的局部空间联系形式

猜你喜欢

转载自www.cnblogs.com/boom-meal/p/12388852.html
今日推荐