【问题标题】:Get the Minimum and Maximum value within specific date range in DataFrame获取DataFrame中特定日期范围内的最小值和最大值
【发布时间】:2020-09-01 23:50:01
【问题描述】:

我有一个 DataFrame,其中包含“From”(日期时间)、“To”(日期时间)列。表格不同行的范围有一些重叠。

这是标准数据框的简化版本(日期范围各不相同且相互重叠):

df1= pd.DataFrame({'From': pd.date_range(start='2020-01-01', end='2020-01-31',freq='2D'), 'To': pd.date_range(start='2020-01-05', end='2020-02-04',freq='2D')})

    From    To
0   2020-01-01  2020-01-05
1   2020-01-03  2020-01-07
2   2020-01-05  2020-01-09
3   2020-01-07  2020-01-11
4   2020-01-09  2020-01-13
5   2020-01-11  2020-01-15
6   2020-01-13  2020-01-17
7   2020-01-15  2020-01-19
8   2020-01-17  2020-01-21
9   2020-01-19  2020-01-23
10  2020-01-21  2020-01-25
11  2020-01-23  2020-01-27
12  2020-01-25  2020-01-29
13  2020-01-27  2020-01-31
14  2020-01-29  2020-02-02
15  2020-01-31  2020-02-04

我有一个数据框,可以像这样保持每日高低值

random.seed(0)
df2= pd.DataFrame({'Date': pd.date_range(start='2020-01-01', end='2020-01-31'), 'High': [random.randint(7,15)+5 for i in range(31)], 'Low': [random.randint(0,7)-1 for i in range(31)]})

    Date    High    Low
0   2020-01-01  18  6
1   2020-01-02  18  6
2   2020-01-03  12  3
3   2020-01-04  16  -1
4   2020-01-05  20  -1
5   2020-01-06  19  0
6   2020-01-07  18  5
7   2020-01-08  16  -1
8   2020-01-09  19  6
9   2020-01-10  17  4
10  2020-01-11  15  2
11  2020-01-12  20  4
12  2020-01-13  14  0
13  2020-01-14  16  2
14  2020-01-15  14  2
15  2020-01-16  13  2
16  2020-01-17  16  1
17  2020-01-18  20  6
18  2020-01-19  14  0
19  2020-01-20  16  0
20  2020-01-21  13  4
21  2020-01-22  13  6
22  2020-01-23  17  0
23  2020-01-24  19  3
24  2020-01-25  20  3
25  2020-01-26  13  0
26  2020-01-27  17  4
27  2020-01-28  18  2
28  2020-01-29  17  3
29  2020-01-30  15  6
30  2020-01-31  20  0

然后我希望根据df1中的From Date和To Date得到最大值和最小值,下面是预期的结果:

result = pd.DataFrame({'From': pd.date_range(start='2020-01-01', end='2020-01-31',freq='2D'), 'To': pd.date_range(start='2020-01-05', end='2020-02-04',freq='2D'), 'High':[20,20,20,19,20,20,16,20,20,17,20,20,20,20,20,20], 'Low':[-1,-1,-1,-1,0,0,1,0,0,0,0,0,0,0,0,0]})

    From    To  High    Low
0   2020-01-01  2020-01-05  20  -1
1   2020-01-03  2020-01-07  20  -1
2   2020-01-05  2020-01-09  20  -1
3   2020-01-07  2020-01-11  19  -1
4   2020-01-09  2020-01-13  20  0
5   2020-01-11  2020-01-15  20  0
6   2020-01-13  2020-01-17  16  1
7   2020-01-15  2020-01-19  20  0
8   2020-01-17  2020-01-21  20  0
9   2020-01-19  2020-01-23  17  0
10  2020-01-21  2020-01-25  20  0
11  2020-01-23  2020-01-27  20  0
12  2020-01-25  2020-01-29  20  0
13  2020-01-27  2020-01-31  20  0
14  2020-01-29  2020-02-02  20  0
15  2020-01-31  2020-02-04  20  0

我尝试使用重采样方法,但似乎不支持自定义日期范围。我正在寻找一种相当有效和优雅的方式来做到这一点。非常感谢。

【问题讨论】:

  • 我假设在您的真实情况下,fromto 之间的差异并不总是 5 天的间隔,对吧?另外,您的两个数据框的大小是多少?
  • 是的,“from”和“to”之间的区别并不固定。 df1 的大小约为 400 万行。 df2 的大小约为 10000 行。感谢您的评论

标签: python pandas dataframe datetime


【解决方案1】:

随着数据的大小,我认为你应该考虑另一种方法,这个想法是通过 df1 按块矢量化日期与 df2 之间的比较。它的行数比其他解决方案多得多,但对于大型数据帧来说会更快。

# this is a parameter you can play with, 
# but if your df1 is in memory, this value should work
nb_split = int((len(df1)*len(df2))//4e6)+1

# work with arrays of flaot
arr1 = df1[['From','To']].astype('int64').to_numpy().astype(float)
arr2 = df2.astype('int64').to_numpy().astype(float)
# create result array
arr_out = np.zeros((len(arr1), 2), dtype=float)
i = 0 #index position
for arr1_sp in np.array_split(arr1, nb_split, axis=0):
    # get length of the chunk
    lft = len(arr1_sp)
    # get the min datetime in From and max in To
    min_from = arr1_sp[:, 0].min()
    max_to = arr1_sp[:, 1].max()

    # select the rows of arr2 tht are within the min and max date of the split
    arr2_sp = arr2[(arr2[:,0]>=min_from)&(arr2[:,0]<=max_to), :]

    # create an bool arraywith True when the date in arr2_sp is above from and below to
    # each row is the reuslt for each row of arr1_sp
    m = np.less_equal.outer(arr1_sp[:,0], arr2_sp[:, 0])\
        &np.greater_equal.outer(arr1_sp[:,1], arr2_sp[:, 0])

    # use this mask to get the values high and low within the range row-wise
    # and replace where the mask was False by np.nan
    arr_high = arr2_sp[:,1]*m
    arr_high[~m] = np.nan
    arr_low = arr2_sp[:,2]*m
    arr_low[~m] = np.nan

    # put the result in the result array
    arr_out[i:i+lft, 0] = np.nanmax(arr_high, axis=1)
    arr_out[i:i+lft, 1] = np.nanmin(arr_low, axis=1)
    i += lft #update first idx position for next loop

# create the columns in df1
df1['High'] = arr_out[:, 0]
df1['Low'] = arr_out[:, 1]

我尝试了 df1 10000 行和 df2 5000 行,这个方法大约是 102ms,而 apply getHighLow2 大约是 8s,所以这种方法快了 80 倍。在相同的地方添加结果。

【讨论】:

  • 太棒了,您的方法加快了整个过程。我可以在 1 分钟内完成工作。太酷了。非常感谢
  • @Seelfun 我一直在研究类似的问题(df1 140K 和 df2 50K),但是我对您的数据大小的预期是 1 分钟。我相信你甚至可以优化它,但即使你每天运行几次,也不确定是否值得:)
  • 我是使用 python 和 numpy 的新手。我不知道如何优化它。但我相信这个速度对于日常使用来说确实足够了。 Anyany,感谢您的帮助。
【解决方案2】:

这是一个执行此操作的函数:

  • 检查从/到间隔中的日期
  • 分别获取HighLow列的最大值和最小值
def get_high_low(d1):

    high = df2.loc[df2["Date"].isin(pd.date_range(d1["From"], d1["To"])), "High"].max()
    low = df2.loc[df2["Date"].isin(pd.date_range(d1["From"], d1["To"])), "Low"].max()

    return pd.Series([high, low], index=["High", "Low"])

然后我们可以应用这个函数并将结果与​​日期连接起来。

pd.concat([df1, df1.apply(get_high_low, axis=1)], axis=1)

结果

    From    To  High    Low
0   2020-01-01  2020-01-05  19  4
1   2020-01-03  2020-01-07  17  5
2   2020-01-05  2020-01-09  19  5
3   2020-01-07  2020-01-11  19  2
4   2020-01-09  2020-01-13  17  4
5   2020-01-11  2020-01-15  19  4
6   2020-01-13  2020-01-17  19  5
7   2020-01-15  2020-01-19  18  5
8   2020-01-17  2020-01-21  18  0
9   2020-01-19  2020-01-23  19  3
10  2020-01-21  2020-01-25  19  5
11  2020-01-23  2020-01-27  19  5
12  2020-01-25  2020-01-29  17  5
13  2020-01-27  2020-01-31  17  3
14  2020-01-29  2020-02-02  17  1
15  2020-01-31  2020-02-04  13  -1

【讨论】:

  • 非常感谢。这是一个很好的尝试。我还没有考虑过。但是我认为如果数据集非常大,花费的时间会很长。我需要测试一下它的性能。
【解决方案3】:

我会做一个交叉合并和查询,然后 groupby:

(df1.assign(dummy=1)
   .merge(df2.assign(dummy=1), on='dummy')   # this is cross merge
   .drop('dummy', axis=1)                    # remove the `dummy` column
   .query('From<=Date<=To')                  # only choose valid data
   .groupby(['From','To'])                   # groupby `From` and `To`
   .agg({'High':'max','Low':'min'})          # aggregation
   .reset_index()                            
)

输出:

         From         To  High  Low
0  2020-01-01 2020-01-05    20   -1
1  2020-01-03 2020-01-07    20   -1
2  2020-01-05 2020-01-09    20   -1
3  2020-01-07 2020-01-11    19   -1
4  2020-01-09 2020-01-13    20    0
5  2020-01-11 2020-01-15    20    0
6  2020-01-13 2020-01-17    16    0
7  2020-01-15 2020-01-19    20    0
8  2020-01-17 2020-01-21    20    0
9  2020-01-19 2020-01-23    17    0
10 2020-01-21 2020-01-25    20    0
11 2020-01-23 2020-01-27    20    0
12 2020-01-25 2020-01-29    20    0
13 2020-01-27 2020-01-31    20    0
14 2020-01-29 2020-02-02    20    0
15 2020-01-31 2020-02-04    20    0

【讨论】:

  • 谢谢。我可以在小规模数据中使用它。但是当我在全集数据中使用代码时,即df1的大小约为400万行,df2的大小约为8000行,代码无法执行.....
【解决方案4】:

您可以创建一个简单的函数来获取给定日期范围内的最小值和最大值。比使用 apply 函数添加列。

def MaxMin(row):
    dfRange = df2[(df2['Date']>=row['From'])&(df2['Date']<=row['To'])] # df2 rows within a given date range
    row['High'] = dfRange['High'].max()
    row['Low'] = dfRange['Low'].min()
    return row

df1 = df1.apply(MaxMin, axis =1)

【讨论】:

  • 谢谢,好像没问题,我现在正在测试性能问题,因为df1的大小约为400万行,df2的大小约为8000行。希望它可以在一个小时左右完成计算。
【解决方案5】:

定义如下函数:

def getHighLow(row):
    wrk = df2[df2.Date.between(row.From, row.To)]
    return pd.Series([wrk.High.max(), wrk.Low.min()], index=['High', 'Low'])

然后运行:

df1.join(df1.apply(getHighLow, axis=1))

根据DRY规则,最好找到wrk(之间的一组行 给定日期)一次,然后(形成wrk)提取最大High和 最小

与其他解决方案相比的另一个优势:我的代码运行速度快了大约 30 %(至少在我的计算机上,使用 %timeit 执行测量)。

编辑

更快的解决方案是 df2 中的搜索可以按索引执行 而不是“来自常规列”。

作为准备步骤运行:

df2a = df2.set_index('Date')

然后定义getHighLow函数的另一个变体:

def getHighLow2(row):
    wrk = df2a.loc[row.From : row.To]
    return pd.Series([wrk.High.max(), wrk.Low.min()], index=['High', 'Low'])

要得到结果,运行:

df1.join(df1.apply(getHighLow2, axis=1))

对于您的数据,执行时间大约是其他解决方案的 一半 (不包括创建df2a的时间,但可以直接以这种形式创建(以Date为索引)。

【讨论】:

  • 是的,你是对的。我的全套数据df1 = 400万行,df2 = 8000+行,第一个代码的执行时间大约150分钟,第二个代码大约75分钟,这比使用逐行循环的方法快得多。
猜你喜欢
  • 1970-01-01
  • 2019-11-13
  • 2020-10-16
  • 1970-01-01
  • 2016-07-09
  • 2021-07-04
  • 2015-05-18
  • 1970-01-01
  • 2019-11-08
相关资源
最近更新 更多