计算莫兰指数和Geary’s C 空间自相关程度


卷积核类型

image-20221006165257287

常见的卷积核为Rook,Bishop,Queen,如上图所示。

Molan’s I

image-20221006165428724

Geary’s C

image-20221006165334932

代码实现为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 利用空间统计量Moran和Geary计算遥感数据的自相关程度
import numpy as np
import pandas as pd

def getMoranV(path,t=0,method="Moran"):
'''
:param path: 图像路径
:param t: 卷积核类型
:param method: 空间自相关方法
:return:
'''
# 读取数据
wb1 = pd.read_excel(path, index_col=0)
data = wb1.values
n, m = wb1.shape

# 定义卷积核

# queen 卷积
dir2 = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
# Rook 卷积
dir1 = [[1, 0], [0, 1], [-1, 0], [0, -1]]
# Bishop 卷积
dir3=[[1,1],[1,-1],[-1,1],[-1,-1]]

if t==0:
dir=dir1
elif t==1:
dir=dir2
else:
dir=dir3

# 开辟O(n^4)权重矩阵
# 这玩意很耗内存,所以可以考虑用局部计算代替全局计算
a = [[0]*(n*m) for _ in range((n*m))]
for i in range(n):
for j in range(m):
# 二维的点转到一维
loc = i * m + j # 一维上的坐标
# 找到他在二维上的临界点
for detX, detY in dir:
x = i + detX
y = j + detY
# 转化为一维
# 边界处理
if 0 <= x < m and 0 <= y < m:
a[loc][x * m + y] = 1 # 表示临接

def moranIndex(data,weight):
# 莫兰指数计算
s_0=np.sum(np.sum(weight))
n,m=len(data),len(data[0])
x_hat=np.mean(data)
up_sum=0
down_sum=0
for i in range(n):
for j in range(m):
# 下一轮
for x in range(n):
for y in range(m):
if not (val:=weight[i*m+j][x*m+y]):
continue
up_sum+=val*(data[i][j]-x_hat)*(data[x][y]-x_hat)
down_sum+=(data[i][j]-x_hat)**2
return (n*m/s_0)*(up_sum/down_sum)

def Geary_C(data,weight):
# Geary's C计算
s_0=np.sum(np.sum(weight))*2
n, m = len(data), len(data[0])
x_hat = np.mean(data)
up_sum = 0
down_sum = 0
for i in range(n):
for j in range(m):
# 下一轮
for x in range(n):
for y in range(m):
if not (val := weight[i * m + j][x * m + y]):
continue
up_sum += val * (data[i][j] - data[x][y])**2
down_sum += (data[i][j] - x_hat) ** 2
return ((n*m-1)/(s_0))*(up_sum/down_sum)

if (M:=method.upper())=="MORAN":
return moranIndex(data,a)
elif M=="GEARY":
return Geary_C(data,a)

def getHelp():
print("Method: {'Moran','Geary'}")
print("t: {0:Rook,1:Queen,2:bishop}")

if __name__ == '__main__':
getHelp()
path = r"data.xlsx"
print("墨兰指数为: %s"%getMoranV(path,0))
print("Geary's C为: %s"%getMoranV(path,0,"Geary"))

输出结果为

1
2
3
4
Method: {'Moran','Geary'}
t: {0:Rook,1:Queen,2:bishop}
墨兰指数为: 0.5579039646993681
Geary's C为: 0.4355131948652942

优化

我们发现,这样子需要开辟n4n^4大小的权重矩阵,且需要O(4n2+n4)O(4n^2+n^4)也就是O(n4)O(n^4)的时间复杂度,那能不能对其做一些剪枝,降低复杂度呢?

我们可以动态开辟空间权重矩阵,从而将空间复杂度降低到O(Mn2)O(M*n^2),其中,MM表示卷积核的有效大小。(非空洞大小)

这样子也能将时间复杂度降低为O(Mn2)O(M*n^2) ,也就是O(n2)O(n^2)级别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 利用空间统计量Moran和Geary计算遥感数据的自相关程度
import numpy as np
import pandas as pd

def getMoranV(path,val=1,t=0,method="Moran"):
'''
:param path: 图像路径
:param t: 卷积核类型
:param method: 空间自相关方法
:param val: 空间权重
:return:
'''
# 读取数据
wb1 = pd.read_excel(path, index_col=0)
data = wb1.values
n, m = wb1.shape

# 定义卷积核

# queen 卷积
dir2 = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
# Rook 卷积
dir1 = [[1, 0], [0, 1], [-1, 0], [0, -1]]
# Bishop 卷积
dir3=[[1,1],[1,-1],[-1,1],[-1,-1]]

if t==0:
dir=dir1
elif t==1:
dir=dir2
else:
dir=dir3

# 开辟O(n^4)权重矩阵
# 这玩意很耗内存,所以可以考虑用局部计算代替全局计算
a = [[] for _ in range(n*m)]
a_weight=0
for i in range(n):
for j in range(m):
# 找到他在二维上的临界点
loc=i*m+j
for detX, detY in dir:
x = i + detX
y = j + detY
# 转化为一维
# 边界处理
if 0 <= x < n and 0 <= y < m:
a[loc].append([x,y,val])
a_weight+=val

def moranIndex(data,weight):
# 莫兰指数计算
# s_0=np.sum(np.sum(weight))
s_0=a_weight
n,m=len(data),len(data[0])
x_hat=np.mean(data)
up_sum=0
down_sum=0
for i in range(n):
for j in range(m):
# 下一轮
loc=i*m+j
for v in weight[loc]:
up_sum+=v[2]*(data[i][j]-x_hat)*(data[v[0]][v[1]]-x_hat)
down_sum+=(data[i][j]-x_hat)**2
return (n*m/s_0)*(up_sum/down_sum)

def Geary_C(data,weight):
# Geary's C计算
s_0=a_weight*2
n, m = len(data), len(data[0])
x_hat = np.mean(data)
up_sum = 0
down_sum = 0
for i in range(n):
for j in range(m):
# 下一轮
loc = i * m + j
for v in weight[loc]:

up_sum += v[2] * (data[i][j] - data[v[0]][v[1]])**2
down_sum += (data[i][j] - x_hat) ** 2
return ((n*m-1)/(s_0))*(up_sum/down_sum)

if (M:=method.upper())=="MORAN":
return moranIndex(data,a)
elif M=="GEARY":
return Geary_C(data,a)

def getHelp():
print("Method: {'Moran','Geary'}")
print("t: {0:Rook,1:Queen,2:bishop}")

if __name__ == '__main__':
getHelp()
path = r"\data.xlsx"
print("墨兰指数为: %s"%getMoranV(path,val=1,t=0))
print("Geary's C为: %s"%getMoranV(path,val=1,t=0,method="Geary"))


栗子----海洋表面温度(SST)逐月计算空间自相关变化情况

数据

image-20221017153246439

dim0表示时间维度,dim1表示维度,dim2表示经度

我们需要对数据进行处理,此时并不是excel数据,直接转为DataFrame,并在API中修改:

1
wb1 = path.read_excel(path)

1
wb1=path

处理

逐月遍历数据即可

1
2
3
List=[]
for i in range(data.shape[0]):
List.append(getMoranV(pd.DataFrame(data[i]),val=1,t=1))

结果可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
plt.figure(figsize=(20,6))
plt.plot(range((v:=data.shape[0])),List,'r-')
Mean=sum(List)/(len(List))
plt.plot(range(v),[Mean]*v,'b',linestyle='--',alpha=0.2)
plt.title("基于Queen邻接的SST空间自相关月变化")
plt.ylabel("时间")
plt.xlabel("日期")
T=[]
M,Y=1,1990
for i in range(360):
T.append("%s年%s月"%(Y,M))
if M%12==0:
M=1
Y+=1
else:
M+=1
plt.xticks(range(360)[::20],T[::20])
plt.show()

下载


绘制逐月动图

1
2
3
4
5
6
7
8
List=[[]]
t=0
while t<data.shape[0]:

if len(List[-1])==12:
List.append([])
List[-1].append(getMoranV(pd.DataFrame(data[t]),val=1,t=1))
t+=1

我们通过二维列表的方式对年份加以区分,这也是常见的手段。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 逐年绘制
path=r"\yourpath"
timeList=[]
for i in range(len(List)):
lis=List[i]
plt.figure(figsize=(20,6))
plt.plot(range(12),lis,'r-')
Mean=sum(lis)/12
plt.plot(range(12),[Mean]*12,'b',linestyle='--',alpha=0.2)
plt.title("%s年"%(i+1990))
plt.xticks(range(12),["%s月"%(m+1) for m in range(12)])
plt.savefig((val:=path+"\\"+"%s.png"%i),dpi=200)
timeList.append(val)

接着就是正常的绘制,我们需要将图像存储下来,并记录存储位置。

通过imageio模块制作动图。

1
2
3
4
5
6
IMG=[]
import imageio
path=r"C:\Users\lenovo\Desktop\Ocean\Spatial"
for i in timeList:
IMG.append(imageio.imread(i))
imageio.mimsave(path+"\\"+"GIF1.gif",IMG,"GIF",duration=1)

GIF1