NumPy:计算去除NaN的平均值

42

我该如何计算矩阵中的平均值,但要在计算时排除掉nan值?(对于R语言用户来说,类似于na.rm = TRUE)。

以下是我尝试过的例子:

import numpy as np
dat = np.array([[1, 2, 3],
                [4, 5, np.nan],
                [np.nan, 6, np.nan],
                [np.nan, np.nan, np.nan]])
print(dat)
print(dat.mean(1))  # [  2.  nan  nan  nan]

去除 NaN 后,我期望的输出结果是:

array([ 2.,  4.5,  6.,  nan])

20
自numpy 1.8版本以来,已经提供了nanmean和nanstd函数。 - Roman Shapovalov
12个回答

35

我认为你需要的是一个掩码数组:

dat = np.array([[1,2,3], [4,5,'nan'], ['nan',6,'nan'], ['nan','nan','nan']])
mdat = np.ma.masked_array(dat,np.isnan(dat))
mm = np.mean(mdat,axis=1)
print mm.filled(np.nan) # the desired answer

编辑:组合所有的时间数据

   from timeit import Timer
    
    setupstr="""
import numpy as np
from scipy.stats.stats import nanmean    
dat = np.random.normal(size=(1000,1000))
ii = np.ix_(np.random.randint(0,99,size=50),np.random.randint(0,99,size=50))
dat[ii] = np.nan
"""  

    method1="""
mdat = np.ma.masked_array(dat,np.isnan(dat))
mm = np.mean(mdat,axis=1)
mm.filled(np.nan)    
"""
    
    N = 2
    t1 = Timer(method1, setupstr).timeit(N)
    t2 = Timer("[np.mean([l for l in d if not np.isnan(l)]) for d in dat]", setupstr).timeit(N)
    t3 = Timer("np.array([r[np.isfinite(r)].mean() for r in dat])", setupstr).timeit(N)
    t4 = Timer("np.ma.masked_invalid(dat).mean(axis=1)", setupstr).timeit(N)
    t5 = Timer("nanmean(dat,axis=1)", setupstr).timeit(N)
    
    print 'Time: %f\tRatio: %f' % (t1,t1/t1 )
    print 'Time: %f\tRatio: %f' % (t2,t2/t1 )
    print 'Time: %f\tRatio: %f' % (t3,t3/t1 )
    print 'Time: %f\tRatio: %f' % (t4,t4/t1 )
    print 'Time: %f\tRatio: %f' % (t5,t5/t1 )

返回:

Time: 0.045454  Ratio: 1.000000
Time: 8.179479  Ratio: 179.950595
Time: 0.060988  Ratio: 1.341755
Time: 0.070955  Ratio: 1.561029
Time: 0.065152  Ratio: 1.433364

1
我认为scipy.nanmean应该是你尝试的第一件事。不知道它还慢吗? - mathtick
@mathtick,实现OP所要求的功能有多种方法。我提供了一种比其他建议的方法更冗长但更快的方法,在我的机器上至少如此(即使使用更新版本的scipy和numpy,这仍然是正确的)。 - JoshAdel
4
此外,据我所知,在scipy 0.10或0.11中没有scipy.nanmean方法。有scipy.stats.stats.nanmeanscipy.stats.nanmean,它们是等效的,我已经测试过了。 - JoshAdel
我在一维中进行了测试,np.nansum(dat) / np.sum(~np.isnan(dat))np.mean(np.ma.masked_array(dat, np.isnan(dat))) 稍微快一些。然而,正如先前指出的那样,瓶颈是更快的10倍。 - Dr. Jan-Philip Gehrcke
似乎np.nansum(dat)是最好的选择。 `Python 2.7.11 |Anaconda 2.4.1 (64-bit) IPython 4.0.1In[190]: %timeit method1() 100次循环,3次取最佳结果:每个循环7.09毫秒 In[191]: %timeit [np.mean([l for l in d if not np.isnan(l)]) for d in dat] 1次循环,3次取最佳结果:每个循环1.04秒 In[192]: %timeit np.array([r[np.isfinite(r)].mean() for r in dat]) 10次循环,3次取最佳结果:每个循环19.6毫秒 In[193]: %timeit np.ma.masked_invalid(dat).mean(axis=1) 100次循环,3次取最佳结果:每个循环11.8毫秒 In[194]: %timeit nanmean(dat,axis=1) 100次循环,3次取最佳结果:每个循环6.36毫秒` - Sklavit
显示剩余2条评论

19

12

从numpy 1.8(于2013年10月30日发布)开始,nanmean确切地实现了您所需的功能:

>>> import numpy as np
>>> np.nanmean(np.array([1.5, 3.5, np.nan]))
2.5

12

5
仅仅为了完整性,因为我已经计时了所有其他的代码——stats.stats.nanmeannp.ma 的解决方案慢大约1.5倍。 - JoshAdel

8

您总是可以在类似以下的地方找到解决方法:

numpy.nansum(dat, axis=1) / numpy.sum(numpy.isfinite(dat), axis=1)

Numpy 2.0的numpy.mean有一个skipna选项,可以解决这个问题。

8
可以即时创建一个过滤掉NaN值的掩码数组:
print np.ma.masked_invalid(dat).mean(1)

我之前没有想过使用这个方法。这是一个不错的一行代码,但在我的测试中它仍然比我的解决方案慢了大约1.5-2倍。尽管如此,感谢让我接触到一个我之前没有注意到的np.ma方法。 - JoshAdel

3

这是建立在JoshAdel所提出的解决方案基础上。

定义如下函数:

def nanmean(data, **args):
    return numpy.ma.filled(numpy.ma.masked_array(data,numpy.isnan(data)).mean(**args), fill_value=numpy.nan)

使用示例:

data = [[0, 1, numpy.nan], [8, 5, 1]]
data = numpy.array(data)
print data
print nanmean(data)
print nanmean(data, axis=0)
print nanmean(data, axis=1)

将打印出:

[[  0.   1.  nan]
 [  8.   5.   1.]]

3.0

[ 4.  3.  1.]

[ 0.5         4.66666667]

3
使用Pandas如何完成这个任务:
import numpy as np
import pandas as pd
dat = np.array([[1, 2, 3], [4, 5, np.nan], [np.nan, 6, np.nan], [np.nan, np.nan, np.nan]])
print dat
print dat.mean(1)

df = pd.DataFrame(dat)
print df.mean(axis=1)

提供:

0    2.0
1    4.5
2    6.0
3    NaN

1

所有提出的方法再进行一次速度检查:

Python 2.7.11 |Anaconda 2.4.1 (64-bit)| (default, Jan 19 2016, 12:08:31) [MSC v.1500 64 bit (AMD64)]
IPython 4.0.1 -- An enhanced Interactive Python.

import numpy as np
from scipy.stats.stats import nanmean    
dat = np.random.normal(size=(1000,1000))
ii = np.ix_(np.random.randint(0,99,size=50),np.random.randint(0,99,size=50))
dat[ii] = np.nan
In[185]: def method1():
    mdat = np.ma.masked_array(dat,np.isnan(dat))
    mm = np.mean(mdat,axis=1)
    mm.filled(np.nan) 

In[190]: %timeit method1()
100 loops, best of 3: 7.09 ms per loop
In[191]: %timeit [np.mean([l for l in d if not np.isnan(l)]) for d in dat]
1 loops, best of 3: 1.04 s per loop
In[192]: %timeit np.array([r[np.isfinite(r)].mean() for r in dat])
10 loops, best of 3: 19.6 ms per loop
In[193]: %timeit np.ma.masked_invalid(dat).mean(axis=1)
100 loops, best of 3: 11.8 ms per loop
In[194]: %timeit nanmean(dat,axis=1)
100 loops, best of 3: 6.36 ms per loop
In[195]: import bottleneck as bn
In[196]: %timeit bn.nanmean(dat,axis=1)
1000 loops, best of 3: 1.05 ms per loop
In[197]: from scipy import stats
In[198]: %timeit stats.nanmean(dat)
100 loops, best of 3: 6.19 ms per loop

所以最好的方法是 'bottleneck.nanmean(dat, axis=1)','scipy.stats.nanmean(dat)' 不如 numpy.nanmean(dat, axis=1) 快。

1

或者你可以使用最新上传的laxarray,它是一个包装器,其中包括掩码数组。

import laxarray as la
la.array(dat).mean(axis=1)

按照JoshAdel的协议,我获得了以下内容:
Time: 0.048791  Ratio: 1.000000   
Time: 0.062242  Ratio: 1.275689   # laxarray's one-liner

所以laxarray稍微慢一点(需要检查原因,可能可以修复),但更易于使用,并允许使用字符串标记维度。
请查看: https://github.com/perrette/laxarray 编辑:我已经检查了另一个模块“la”,larry,它击败了所有测试:
import la
la.larry(dat).mean(axis=1)

By hand, Time: 0.049013 Ratio: 1.000000
Larry,   Time: 0.005467 Ratio: 0.111540
laxarray Time: 0.061751 Ratio: 1.259889

令人印象深刻!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接