我有一套46年的降雨量数据。它是46个numpy数组的形式,每个数组的形状为145, 192
,所以每年在给定模型中每个lat和lon坐标的最大降雨量数据的不同数组。
我需要通过对46年来每个坐标进行M-K测试(Mann-Kendall)来创建tau值的全局图。
我还在学习python,所以我一直难以找到一种方法来以简单的方式浏览所有数据,而不涉及为每个坐标制作27840个新数组。
到目前为止,我已经研究了如何使用scipy.stats.kendalltau
并使用此处的定义:https://github.com/mps9506/Mann-Kendall-Trend
修改
为了澄清并添加更多细节,我需要对每个坐标执行测试,而不是单独对每个文件执行测试。例如,对于第一次MK测试,我希望我的x = 46,我希望y = data1 [0,0],data2 [0,0],data3 [0,0] ... data46 [0,0 ]。然后为每个数组中的每个坐标重复此过程。总共M-K测试将完成27840次,并留下27840个tau值,然后我可以在全球地图上绘制。
编辑2:
我现在遇到了另一个问题。关闭建议的代码,我有以下内容:
for i in range(145):
for j in range(192):
out[i,j] = mk_test(yrmax[:,i,j],alpha=0.05)
print out
我使用numpy.stack
将所有46个数组堆叠成一个形状为(46L, 145L, 192L)
的单个数组(yrmax)我已经测试了它,如果我从中更改代码,它会正确计算p和tau [i,j]刚出来。但是,这样做会使for循环变得混乱,因此它只取代最后一个坐标的结果而不是所有的坐标。如果我保留上面的代码,我会收到错误:TypeError: list indices must be integers, not tuple
我的第一个猜测是它与mk_test以及如何在定义中返回信息有关。所以我尝试改变上面链接中的代码来改变数据的返回方式,但是我一直收到与元组有关的错误。所以现在我不确定它出了什么问题以及如何解决它。
编辑3:
我认为应该补充一点澄清。我已经修改了链接中的定义,因此它只返回我想要创建地图,p和z的两个数字值。
答案 0 :(得分:1)
我认为这不像你想象的那么大。根据您的描述,听起来您实际上并不想要scipy kendalltau,而是您发布的存储库中的函数。这是我设置的一个小例子:
from time import time
import numpy as np
from mk_test import mk_test
data = np.array([np.random.rand(145, 192) for _ in range(46)])
mk_res = np.empty((145, 192), dtype=object)
start = time()
for i in range(145):
for j in range(192):
out[i, j] = mk_test(data[:, i, j], alpha=0.05)
print(f'Elapsed Time: {time() - start} s')
Elapsed Time: 35.21990394592285 s
我的系统是配备16 GB Ram的MacBook Pro 2.7 GHz Intel Core I7,所以没什么特别的。
mk_res数组中的每个条目(形状145,192)对应于您的一个坐标点,并包含如下条目:
array(['no trend', 'False', '0.894546014835', '0.132554125342'], dtype='<U14')
可能有用的一件事是修改mk_test.py中的代码以返回所有数值。因此,不是'无趋势'/'正''/'否定',你可以返回0/1 / -1,并且1/0为True / False,然后你不必担心整个对象数组类型。我不知道您可能想要在下游做什么样的分析,但我想这会先发制人地规避任何令人头痛的问题。
答案 1 :(得分:1)
感谢所提供的答案和一些工作,我能够为其他需要使用Mann-Kendall测试进行数据分析的人提供解决方案。
我需要做的第一件事就是将原来的数组压平成一维数组。我知道可能有一种更简单的方法可以做到这一点,但我最终使用了基于代码Grr建议使用的以下代码。
`x = 46
out1 = np.empty(x)
out = np.empty((0))
for i in range(146):
for j in range(193):
out1 = yrmax[:,i,j]
out = np.append(out, out1, axis=0) `
然后我按如下方式重新生成结果数组(out):
out2 = np.reshape(out,(27840,46))
我这样做了所以我的数据格式与scipy.stats.kendalltau
兼容27840是我在地图上每个坐标上的值的总数(即它只是145 * 192 )46是数据跨越的年数。
然后我使用了以下我从Grr代码修改的循环来找到Kendall-tau及其在46年期间的每个纬度和经度的相应p值。
`x = range(46)
y = np.zeros((0))
for j in range(27840):
b = sc.stats.kendalltau(x,out2[j,:])
y = np.append(y, b, axis=0)`
最后,我将数据重新整形为如图所示:newdata = np.reshape(y,(145,192,2))
因此最终数组采用合适的格式来创建tau和p值的全局映射。
感谢大家的帮助!
答案 2 :(得分:0)
根据您的情况,制作数组可能最简单。
你不会真的需要它们全部在内存中(不是说它听起来像是一个可怕的数据量)。这样的事情只需要立即处理一个“复制出来”的坐标趋势:
SIZE = (145,192)
year_matrices = load_years() # list of one 145x192 arrays per year
result_matrix = numpy.zeros(SIZE)
for x in range(SIZE[0]):
for y in range(SIZE[1]):
coord_trend = map(lambda d: d[x][y], year_matrices)
result_matrix[x][y] = analyze_trend(coord_trend)
print result_matrix
现在,如果你真的想避免实际复制数据,那么像itertools.izip
这样的东西可以帮到你。
以下是Python的“zip”如何与您的数据一起使用的具体示例(尽管您每年都使用ndarray.flatten
):
year_arrays = [
['y0_coord0_val', 'y0_coord1_val', 'y0_coord2_val', 'y0_coord2_val'],
['y1_coord0_val', 'y1_coord1_val', 'y1_coord2_val', 'y1_coord2_val'],
['y2_coord0_val', 'y2_coord1_val', 'y2_coord2_val', 'y2_coord2_val'],
]
assert len(year_arrays) == 3
assert len(year_arrays[0]) == 4
coord_arrays = zip(*year_arrays) # i.e. `zip(year_arrays[0], year_arrays[1], year_arrays[2])`
# original data is essentially transposed
assert len(coord_arrays) == 4
assert len(coord_arrays[0]) == 3
assert coord_arrays[0] == ('y0_coord0_val', 'y1_coord0_val', 'y2_coord0_val', 'y3_coord0_val')
assert coord_arrays[1] == ('y0_coord1_val', 'y1_coord1_val', 'y2_coord1_val', 'y3_coord1_val')
assert coord_arrays[2] == ('y0_coord2_val', 'y1_coord2_val', 'y2_coord2_val', 'y3_coord2_val')
assert coord_arrays[3] == ('y0_coord2_val', 'y1_coord2_val', 'y2_coord2_val', 'y3_coord2_val')
flat_result = map(analyze_trend, coord_arrays)
上面的示例仍然会复制数据(一次全部复制,而不是一次复制!)但希望能够显示正在发生的事情。
现在,如果您将zip
替换为itertools.izip
而map
替换为itertools.map
,那么副本不会发生 - itertools包装原始数组并跟踪它的位置应该从内部获取值。
但是有一个问题:利用itertools只能按顺序访问数据(即通过迭代)。在您的情况下,看起来https://github.com/mps9506/Mann-Kendall-Trend/blob/master/mk_test.py处的代码可能与此不兼容。 (我没有查看算法本身,看它是否可以。)
另请注意,在示例中,我已经掩盖了numpy ndarray的东西,只显示平面坐标数组。看起来numpy有一些自己的选项来处理这个而不是itertools,例如this answer说“采用数组的转置不会复制”。你的问题有点笼统,所以我试着给出一些关于如何处理Python中更大数据的一般提示。
答案 3 :(得分:0)
我遇到了同样的任务,并设法使用 numpy
和 scipy
提出了一个矢量化解决方案。
公式与本页相同:https://vsp.pnnl.gov/help/Vsample/Design_Trend_Mann_Kendall.htm。
最棘手的部分是计算并列值的调整。我修改了 this answer 中的代码,以矢量化方式计算每个记录的绑定值的数量。
以下是两个函数:
import copy
import numpy as np
from scipy.stats import norm
def countTies(x):
'''Count number of ties in rows of a 2D matrix
Args:
x (ndarray): 2d matrix.
Returns:
result (ndarray): 2d matrix with same shape as <x>. In each
row, the number of ties are inserted at (not really) arbitary
locations.
The locations of tie numbers in are not important, since
they will be subsequently put into a formula of sum(t*(t-1)*(2t+5)).
Inspired by: https://stackoverflow.com/a/24892274/2005415.
'''
if np.ndim(x) != 2:
raise Exception("<x> should be 2D.")
m, n = x.shape
pad0 = np.zeros([m, 1]).astype('int')
x = copy.deepcopy(x)
x.sort(axis=1)
diff = np.diff(x, axis=1)
cated = np.concatenate([pad0, np.where(diff==0, 1, 0), pad0], axis=1)
absdiff = np.abs(np.diff(cated, axis=1))
rows, cols = np.where(absdiff==1)
rows = rows.reshape(-1, 2)[:, 0]
cols = cols.reshape(-1, 2)
counts = np.diff(cols, axis=1)+1
result = np.zeros(x.shape).astype('int')
result[rows, cols[:,1]] = counts.flatten()
return result
def MannKendallTrend2D(data, tails=2, axis=0, verbose=True):
'''Vectorized Mann-Kendall tests on 2D matrix rows/columns
Args:
data (ndarray): 2d array with shape (m, n).
Keyword Args:
tails (int): 1 for 1-tail, 2 for 2-tail test.
axis (int): 0: test trend in each column. 1: test trend in each
row.
Returns:
z (ndarray): If <axis> = 0, 1d array with length <n>, standard scores
corresponding to data in each row in <x>.
If <axis> = 1, 1d array with length <m>, standard scores
corresponding to data in each column in <x>.
p (ndarray): p-values corresponding to <z>.
'''
if np.ndim(data) != 2:
raise Exception("<data> should be 2D.")
# alway put records in rows and do M-K test on each row
if axis == 0:
data = data.T
m, n = data.shape
mask = np.triu(np.ones([n, n])).astype('int')
mask = np.repeat(mask[None,...], m, axis=0)
s = np.sign(data[:,None,:]-data[:,:,None]).astype('int')
s = (s * mask).sum(axis=(1,2))
#--------------------Count ties--------------------
counts = countTies(data)
tt = counts * (counts - 1) * (2*counts + 5)
tt = tt.sum(axis=1)
#-----------------Sample Gaussian-----------------
var = (n * (n-1) * (2*n+5) - tt) / 18.
eps = 1e-8 # avoid dividing 0
z = (s - np.sign(s)) / (np.sqrt(var) + eps)
p = norm.cdf(z)
p = np.where(p>0.5, 1-p, p)
if tails==2:
p=p*2
return z, p
我假设您的数据采用 (time, latitude, longitude)
的布局,并且您正在检查每个纬度/经度单元的时间趋势。
为了模拟这个任务,我合成了一个形状为 (50, 145, 192)
的样本数据数组。 50
时间点取自 Wilks 2011 年大气科学中的统计方法一书的示例 5.9。然后我简单地将相同的时间序列复制了 27840 次以使其(50, 145, 192)
。
以下是计算:
x = np.array([0.44,1.18,2.69,2.08,3.66,1.72,2.82,0.72,1.46,1.30,1.35,0.54,\
2.74,1.13,2.50,1.72,2.27,2.82,1.98,2.44,2.53,2.00,1.12,2.13,1.36,\
4.9,2.94,1.75,1.69,1.88,1.31,1.76,2.17,2.38,1.16,1.39,1.36,\
1.03,1.11,1.35,1.44,1.84,1.69,3.,1.36,6.37,4.55,0.52,0.87,1.51])
# create a big cube with shape: (T, Y, X)
arr = np.zeros([len(x), 145, 192])
for i in range(arr.shape[1]):
for j in range(arr.shape[2]):
arr[:, i, j] = x
print(arr.shape)
# re-arrange into tabular layout: (Y*X, T)
arr = np.transpose(arr, [1, 2, 0])
arr = arr.reshape(-1, len(x))
print(arr.shape)
import time
t1 = time.time()
z, p = MannKendallTrend2D(arr, tails=2, axis=1)
p = p.reshape(145, 192)
t2 = time.time()
print('time =', t2-t1)
该样本时间序列的 p 值为 0.63341565
,我已针对 pymannkendall
module 结果进行了验证。由于 arr
仅包含 x
的重复副本,因此结果 p
是大小为 (145, 192) 的二维数组,其中包含所有 0.63341565
。
我只用了 1.28 秒就可以计算出来。