如果我在pandas中有一个类似下面的DataFrame:
A B C
0 1 NaN 2
1 NaN 3 NaN
2 NaN 4 5
3 NaN NaN NaN
如何从每行中获取第一个非空值?例如,对于上面的示例,我想获得:[1, 3, 4, None]
(或等效的Series)。
使用fillna
从左侧填充NaN值,然后获取最左侧的列:
df.fillna(method='bfill', axis=1).iloc[:, 0]
这种方法非常混乱,首先使用first_valid_index
获取有效列,将返回的系列转换为数据框以便我们可以逐行调用apply
并使用它来索引回原始数据框:
In [160]:
def func(x):
if x.values[0] is None:
return None
else:
return df.loc[x.name, x.values[0]]
pd.DataFrame(df.apply(lambda x: x.first_valid_index(), axis=1)).apply(func,axis=1)
Out[160]:
0 1
1 3
2 4
3 NaN
dtype: float64
编辑
稍微更干净的方法:
In [12]:
def func(x):
if x.first_valid_index() is None:
return None
else:
return x[x.first_valid_index()]
df.apply(func, axis=1)
Out[12]:
0 1
1 3
2 4
3 NaN
dtype: float64
我认为这比所有提出的方法都快,我会在这里发表意见。 argmin
以向量化的方式给出了np.isnan
结果中每行第一个False
值的索引,这是最困难的部分。它仍然依赖于Python循环来提取值,但查找非常快速:
def get_first_non_null(df):
a = df.values
col_index = np.isnan(a).argmin(axis=1)
return [a[row, col] for row, col in enumerate(col_index)]
编辑: 这里有一个完全向量化的解决方案,根据输入形状,速度可能会更快。以下是更新后的基准测试结果。
def get_first_non_null_vec(df):
a = df.values
n_rows, n_cols = a.shape
col_index = np.isnan(a).argmin(axis=1)
flat_index = n_cols * np.arange(n_rows) + col_index
return a.ravel()[flat_index]
如果一行完全为空,则相应的值也将为空。这是与unutbu的解况对比:
df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 1500), p=(0.01, 0.99)))
#%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 220 ms per loop
100 loops, best of 3: 16.2 ms per loop
100 loops, best of 3: 12.6 ms per loop
In [109]:
df = pd.DataFrame(np.random.choice([1, np.nan], (100000, 150), p=(0.01, 0.99)))
#%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 246 ms per loop
10 loops, best of 3: 48.2 ms per loop
100 loops, best of 3: 15.7 ms per loop
df = pd.DataFrame(np.random.choice([1, np.nan], (1000000, 15), p=(0.01, 0.99)))
%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 326 ms per loop
1 loops, best of 3: 326 ms per loop
10 loops, best of 3: 35.7 ms per loop
%timeit get_frst_non_null(df)
- yangjie以下是另一种方法:
In [183]: df.stack().groupby(level=0).first().reindex(df.index)
Out[183]:
0 1
1 3
2 4
3 NaN
dtype: float64
这里的想法是使用 stack
将列转换为行索引层:
In [184]: df.stack()
Out[184]:
0 A 1
C 2
1 B 3
2 B 4
C 5
dtype: float64
现在,如果你按照第一行层级进行分组 -- 即原始索引 -- 并从每个组中取第一个值,你基本上会得到所需的结果:
In [185]: df.stack().groupby(level=0).first()
Out[185]:
0 1
1 3
2 4
dtype: float64
我们需要做的就是重新索引结果(使用原始索引),以便包含完全为NaN的行:
df.stack().groupby(level=0).first().reindex(df.index)
axis=1
中的groupby
如果我们传递一个返回相同值的可调用函数,我们就可以将所有列分组在一起。这使我们可以使用groupby.agg
,它提供了简化此过程的first
方法。
df.groupby(lambda x: 'Z', 1).first()
Z
0 1.0
1 3.0
2 4.0
3 NaN
这将返回一个数据帧,其中包含我在可调用函数中返回的列名
lookup
、notna
和idxmax
df.lookup(df.index, df.notna().idxmax(1))
array([ 1., 3., 4., nan])
argmin
and slicingv = df.values
v[np.arange(len(df)), np.isnan(v).argmin(1)]
array([ 1., 3., 4., nan])
这并不是什么新鲜的东西,它结合了@yangie的方法和列表推导式的最佳部分,以及@EdChum的df.apply
方法,我认为这是最容易理解的。
首先,我们想从哪些列中挑选值?
In [95]: pick_cols = df.apply(pd.Series.first_valid_index, axis=1)
In [96]: pick_cols
Out[96]:
0 A
1 B
2 B
3 None
dtype: object
In [100]: [df.loc[k, v] if v is not None else None
....: for k, v in pick_cols.iteritems()]
Out[100]: [1.0, 3.0, 4.0, None]
DataFrame
相匹配:In [98]: pd.Series({k:df.loc[k, v] if v is not None else None
....: for k, v in pick_cols.iteritems()})
Out[98]:
0 1
1 3
2 4
3 NaN
dtype: float64
pick_cols
到最终结果的方法。你认为这个操作应该叫什么? - LondonRobfirst_valid_index
是否为None
来处理所有NaN
的行。 - EdChumJoeCondron的回答(编辑前!)很棒,但是通过避免非向量化枚举,仍有很大的改进空间:
def get_first_non_null_vect(df):
a = df.values
col_index = np.isnan(a).argmin(axis=1)
return a[np.arange(a.shape[0]), col_index]
如果DataFrame相对平坦,那么改进的幅度就比较小:
In [4]: df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 1500), p=(0.01, 0.99)))
In [5]: %timeit get_first_non_null(df)
10 loops, best of 3: 34.9 ms per loop
In [6]: %timeit get_first_non_null_vect(df)
10 loops, best of 3: 31.6 ms per loop
...但在精简的数据框中可能会很重要:
In [7]: df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 15), p=(0.1, 0.9)))
In [8]: %timeit get_first_non_null(df)
100 loops, best of 3: 3.75 ms per loop
In [9]: %timeit get_first_non_null_vect(df)
1000 loops, best of 3: 718 µs per loop
与JoeCondron的向量化版本相比,运行时间非常相似(对于较小的数据框来说,这仍然稍微快一些,而对于大型数据框来说则稍微慢一些)。
这里有一个一行解决方案:
[row[row.first_valid_index()] if row.first_valid_index() else None for _, row in df.iterrows()]
编辑:
这个解决方案迭代了df
的每一行。 row.first_valid_index()
返回第一个非NA / null值的标签,这将用作索引以获取每行中的第一个非null项。
如果行中没有非null值,则row.first_valid_index()
将为None,因此无法用作索引,因此我需要一个if-else
语句。
我将所有内容都打包到列表推导式中以简洁明了。
first_valid_index()
两次真是太遗憾了。也许如果你失去一点简洁性,你会获得可读性和效率。 - LondonRobfirst_valid_index()
。谢谢你的建议,也许我稍后会更新。 - yangjiedf=pandas.DataFrame({'A':[1, numpy.nan, numpy.nan, numpy.nan], 'B':[numpy.nan, 3, 4, numpy.nan], 'C':[2, numpy.nan, 5, numpy.nan]})
df
A B C
0 1.0 NaN 2.0
1 NaN 3.0 NaN
2 NaN 4.0 5.0
3 NaN NaN NaN
df.apply(lambda x: numpy.nan if all(x.isnull()) else x[x.first_valid_index()], axis=1).tolist()
[1.0, 3.0, 4.0, nan]