在python中使用带有大量数据的Mann Kendall

时间:2017-10-20 19:47:01

标签: python arrays numpy statistics jupyter-notebook

我有一套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的两个数字值。

4 个答案:

答案 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.izipmap替换为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)

我遇到了同样的任务,并设法使用 numpyscipy 提出了一个矢量化解决方案。

公式与本页相同: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 秒就可以计算出来。