Pandas列列表中每行的第一个非空值

时间:2015-08-05 09:15:52

标签: python pandas

如果我在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](或同等系列)。

9 个答案:

答案 0 :(得分:28)

您不需要使用first_valid_index

df.bfill(axis=1).iloc[:, 0]

答案 1 :(得分:9)

这是一种非常混乱的方法,首先使用first_valid_index获取有效列,将返回的系列转换为数据帧,以便我们可以逐行调用apply并使用它来编制索引回到原来的df:

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

答案 2 :(得分:9)

这是另一种方法:

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)

答案 3 :(得分:9)

我想在这里权衡,因为我认为这比任何提议的方法都要快得多。 argmin以矢量化方式给出False结果的每一行中第一个np.isnan值的索引,这是最难的部分。它仍然依赖于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]

如果一行完全为null,那么相应的值也将为null。 这是针对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

答案 4 :(得分:4)

这不是什么新鲜事,但它是@yangie's approach的最佳位与列表理解的组合,而@EdChum's df.apply approach我认为最容易理解。

首先,我们要从哪些列中选择值?

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

答案 5 :(得分:2)

这是一个单行解决方案:

[row[row.first_valid_index()] if row.first_valid_index() else None for _, row in df.iterrows()]

修改

此解决方案迭代df行。 row.first_valid_index()返回第一个非NA / null值的标签,该值将用作索引以获取每行中的第一个非空项目。

如果行中没有非空值,row.first_valid_index()将为None,因此不能用作索引,因此我需要if-else语句。

为了简洁起见,我把所有内容都打包成了列表解析。

答案 6 :(得分:2)

JoeCondron's answer(编辑:在他最后一次编辑之前!)很酷但是通过避免非向量化枚举可以获得显着改善的余地:

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

...但可以与slim DataFrames相关:

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的矢量化版本相比,运行时非常相似(对于纤薄的DataFrame,这仍然稍微快一点,而对于大型数据框,这稍微慢一点)。

答案 7 :(得分:2)

//book[not(@title="one")]groupby

如果传递一个返回相同值的可调用对象,则将所有列组合在一起。这使我们可以使用axis=1,它为我们提供了简化此过程的groupby.agg方法

first

这将返回一个数据帧,其中包含我在可调用对象中返回的内容的列名


df.groupby(lambda x: 'Z', 1).first() Z 0 1.0 1 3.0 2 4.0 3 NaN lookupnotna

idxmax

df.lookup(df.index, df.notna().idxmax(1)) array([ 1., 3., 4., nan]) 和切片

argmin

答案 8 :(得分:1)

df=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]