在Pandas DataFrame中定位首个和最后一个非NaN值

92 投票
3 回答
96214 浏览
提问于 2025-04-17 22:17

我有一个用日期作为索引的Pandas DataFrame。里面有很多列,但有些列的数据只在时间序列的某些部分有值。我想找出每列中第一个和最后一个非NaN(也就是有值的)数据的位置,这样我就可以提取出这些日期,看看某一列的数据覆盖了多长时间。

有人能告诉我该怎么做吗?

3 个回答

1

这是一个方便的函数,基于behzad.nouri的建议和cs95之前的回答。如果有错误或误解,都是我的问题。

import pandas as pd
import numpy as np

df = pd.DataFrame([["2022-01-01", np.nan, np.nan, 1], ["2022-01-02", 2, np.nan, 2], ["2022-01-03", 3, 3, 3], ["2022-01-04", 4, 4, 4], ["2022-01-05", np.nan, 5, 5]], columns=['date', 'A', 'B', 'C'])
df['date'] = pd.to_datetime(df['date'])

df
#        date    A    B    C
#0 2022-01-01  NaN  NaN  1.0
#1 2022-01-02  2.0  NaN  2.0
#2 2022-01-03  3.0  3.0  3.0
#3 2022-01-04  4.0  4.0  4.0
#4 2022-01-05  NaN  5.0  5.0

我们想从A和B中最早的共同日期开始,到A和B中最新的共同日期结束(无论什么原因,我们不按C列进行筛选)。

# filter data to minimum/maximum common available dates
def get_date_range(df, cols):
    """return a tuple of the earliest and latest valid data for all columns in the list"""
    a,b = df[cols].apply(pd.Series.first_valid_index).max(), df[cols].apply(pd.Series.last_valid_index).min()
    return (df.loc[a, 'date'], df.loc[b, 'date'])

a,b = get_date_range(df, cols=['A', 'B'])
a
#Timestamp('2022-01-03 00:00:00')
b
#Timestamp('2022-01-04 00:00:00')

现在对数据进行筛选:

df.loc[(df.date >= a) & (df.date <= b)]
#        date    A    B    C
#2 2022-01-03  3.0  3.0  3
#3 2022-01-04  4.0  4.0  4
56

这里有一些有用的例子。

序列

s = pd.Series([np.NaN, 1, np.NaN, 3, np.NaN], index=list('abcde'))
s

a    NaN
b    1.0
c    NaN
d    3.0
e    NaN
dtype: float64

# first valid index
s.first_valid_index()
# 'b'

# first valid position
s.index.get_loc(s.first_valid_index())
# 1

# last valid index
s.last_valid_index()
# 'd'

# last valid position
s.index.get_loc(s.last_valid_index())
# 3

使用 notnaidxmax 的另一种解决方案:

# first valid index
s.notna().idxmax()
# 'b'

# last valid index
s.notna()[::-1].idxmax()
# 'd'

数据框

df = pd.DataFrame({
    'A': [np.NaN, 1, np.NaN, 3, np.NaN], 
    'B': [1, np.NaN, np.NaN, np.NaN, np.NaN]
})
df

     A    B
0  NaN  1.0
1  1.0  NaN
2  NaN  NaN
3  3.0  NaN
4  NaN  NaN

(first|last)_valid_index 在数据框中并没有定义,但你可以通过 apply 方法在每一列上使用它们。

# first valid index for each column
df.apply(pd.Series.first_valid_index)

A    1
B    0
dtype: int64

# last valid index for each column
df.apply(pd.Series.last_valid_index)

A    3
B    0
dtype: int64

和之前一样,你也可以使用 notnaidxmax。这种写法稍微自然一些。

# first valid index
df.notna().idxmax()

A    1
B    0
dtype: int64

# last valid index
df.notna()[::-1].idxmax()

A    3
B    0
dtype: int64
74

@behzad.nouri 的解决方案非常有效,能够使用 Series.first_valid_indexSeries.last_valid_index 分别返回第一个和最后一个有效的非 NaN 值。

撰写回答