Pandas DataFrame:如何在行和列范围内获得最小值

Posted

技术标签:

【中文标题】Pandas DataFrame:如何在行和列范围内获得最小值【英文标题】:Pandas DataFrame: How to natively get minimum across range of rows and columns 【发布时间】:2015-10-30 06:04:58 【问题描述】:

我有一个看起来与此类似但有 10,000 行和 500 列的 Pandas DataFrame。

对于每一行,我想找出 3 天前 15:00 和今天 13:30 之间的最小值。

有没有一些原生的 numpy 方法可以快速做到这一点? 我的目标是能够通过说“从 3 天前 15:00 到 0 天前(又名今天)13:30 的最小值是多少?”来获得每一行的最小值?

对于这个特定示例,最后两行的答案是:

2011-01-09 2481.22
2011-01-10 2481.22

我现在的方式是这样的:

1. Get the earliest row (only the values after the start time)
2. Get the middle rows 
3. Get the last row (only the values before the end time)
4. Concat (1), (2), and (3)
5. Get the minimum of (4)

但这在大型 DataFrame 上需要很长时间


下面的代码会生成一个类似的DF:

import numpy
import pandas
import datetime

numpy.random.seed(0)

random_numbers = (numpy.random.rand(10, 8)*100 + 2000)
columns        = [datetime.time(13,0) , datetime.time(13,30), datetime.time(14,0), datetime.time(14,30) , datetime.time(15,0), datetime.time(15,30) ,datetime.time(16,0), datetime.time(16,30)] 
index          = pandas.date_range('2011/1/1', '2011/1/10')
df             = pandas.DataFrame(data = random_numbers, columns=columns, index = index).astype(int)

print df

这是数据框的 json 版本:

'"13:00:00":"1293840000000":2085,"1293926400000":2062,"1294012800000":2035,"1294099200000":2086,"1294185600000":2006,"129409720":2000" “1294358400000”:2078,“1294444800000”:2055,“1294531200000”:2023,“1294617600000”:2024,“13:30:00”:“1293840000000”:2045,“1293926400000”:2039,“1294012800000”: 2035,“1294099200000”:2025,“1294272000000”:2099,“1294358400000”:2028,“1294444800000”:2028,“1294531200000”:2034,“1294617600000”:2010,“14:00: 00“:”1293840000000“:2095,”1293926400000“:2001,”1294099200000“:2032,”1294185600000“:2022,”1294272000000“:2040,”1294358400000“:2024,”1294444800000“: 2070,“1294531200000”:2095,“14:30:00”:2057,“1293926400”:2042,“1294012800000”:2018,“1294099200000”:2023,“1294185600000 ":2025,"1294272000000":2016,"1294358400000":2066,"1294444800000":2041,"1294531200000":2098,"1294617600000":2023,"15:00:00000":"1293080," "1293926400000":2025,"1294012800000":2040,"1294099200000":2061,"129418 5600000":2013,"1294272000000":2063,"1294358400000":2024,"1294444800000":2036,"1294531200000":2096,"1294617600000":2068""1294444800000":2096,"1294617600000":2068"15:30:0000000003:00003" ,“1293926400000”:2092,“1294099200000”:2001,“1294185600000”:2001,“1294272000000”:2049,“1294358400000”:2066,“1294444800000”:2082,“1294531200000”:2090,“ 1294617600000“:2005,”16:00:00“:2081,”1293926400000“:2003,”1294012800000“:2009,”1294099200000“:2001,”1294185600000“:2011,”1294272000000“:2098 ,“1294358400000”:2092,“1294531200000”:2029,“1294617600000”:2073,“16:30:00”:“1293840000000”:2015,“1293926400000”:2095,“1294012800000” :2094,“1294099200000”:2061,“1294272000000”:2006,“1294358400000”:2042,“1294444800000”:2004,“1294531200000”:2099,“1294617600000”:2088'' p >

【问题讨论】:

会先做一个rolling_min 来获得最后3 行每列的最小值,然后再做一个min 来获得新行的最小值,得到你想要的结果吗? 您是如何到达2011-01-10 2481.22 的?你能更详细地解释一下你的预期输出吗? 当然。以 2011-01-10 的行为例。我想在 15:00 之后收集 3 天前(3 行前)的所有值(2011-01-07 15:30 值,2011-01-07 2011-01-07 16:00 值,2011-01 -07 16:30 值)直到今天(2011-01-10)13:30。所以基本上每个单元格都在 2011-01-07 15:30 到今天 13:30 之间。在我收集这些值之后,我得到了一堆的最小值。 在您之前删除的答案之一中,您说“时间范围的确切起点和终点会有所不同”。您能否也对此进行更多说明? @chthonicdaemon 我已经添加了 python 代码来生成这个数据帧以及这个数据帧的 json 版本。列名是 datetime.time 类型,索引是原始版本中的 pandas.DatetimeIndex 类型。 【参考方案1】:

您可以先堆叠 DataFrame 以创建一个系列,然后根据需要对其进行索引切片并取最小值。例如:

first, last = ('2011-01-07', datetime.time(15)), ('2011-01-10', datetime.time(13, 30))
df.stack().loc[first: last].min()

df.stack 的结果是 SeriesMultiIndex,其中内部级别由原始列组成。然后,我们使用 tuple 对与开始和结束日期和时间进行切片。 如果您要进行大量此类操作,那么您应该考虑将df.stack() 分配给某个变量。然后您可能会考虑将索引更改为正确的DatetimeIndex。然后,您可以根据需要使用时间序列和网格格式。

这是另一种避免堆叠的方法,并且在您实际使用的大小的 DataFrame 上速度更快(作为一次性的;切片堆叠的DataFrame 在堆叠后会快得多,所以如果您是执行其中许多操作,您应该堆叠并转换索引)。 它不太通用,因为它适用于minmax,但不适用于mean。它获取第一行和最后一行子集的min 和中间行的min(如果有),并获取这三个候选行的min

first_row = df.index.get_loc(first[0])
last_row = df.index.get_loc(last[0])
if first_row == last_row:
    result = df.loc[first[0], first[1]: last[1]].min()
elif first_row < last_row:
    first_row_min = df.loc[first[0], first[1]:].min()
    last_row_min = df.loc[last[0], :last[1]].min()
    middle_min = df.iloc[first_row + 1:last_row].min().min()
    result = min(first_row_min, last_row_min, middle_min)
else: 
    raise ValueError('first row must be <= last row')

请注意,如果first_row + 1 == last_rowmiddle_minnan,但只要middle_min 在对min 的调用中没有出现在第一位,结果仍然正确。

【讨论】:

【参考方案2】:

举个例子,比较容易理解。

|            | 13:00:00 | 13:30:00 | 14:00:00 | 14:30:00 | 15:00:00 | 15:30:00 | 16:00:00 | 16:30:00 | 
|------------|----------|----------|----------|----------|----------|----------|----------|----------| 
| 2011-01-01 | 2054     | 2071     | 2060     | 2054     | 2042     | 2064     | 2043     | 2089     | 
| 2011-01-02 | 2096     | 2038     | 2079     | 2052     | 2056     | 2092     | 2007     | 2008     | 
| 2011-01-03 | 2002     | 2083     | 2077     | 2087     | 2097     | 2079     | 2046     | 2078     | 
| 2011-01-04 | 2011     | 2063     | 2014     | 2094     | 2052     | 2041     | 2026     | 2077     | 
| 2011-01-05 | 2045     | 2056     | 2001     | 2061     | 2061     | 2061     | 2094     | 2068     | 
| 2011-01-06 | 2035     | 2043     | 2069     | 2006     | 2066     | 2067     | 2021     | 2012     | 
| 2011-01-07 | 2031     | 2036     | 2057     | 2043     | 2098     | 2010     | 2020     | 2016     | 
| 2011-01-08 | 2065     | 2025     | 2046     | 2024     | 2015     | 2011     | 2065     | 2013     | 
| 2011-01-09 | 2019     | 2036     | 2082     | 2009     | 2083     | 2009     | 2097     | 2046     | 
| 2011-01-10 | 2097     | 2060     | 2073     | 2003     | 2028     | 2012     | 2029     | 2011     | 

假设我们想要找到从 (2, b) 到 (6, d) 每一行的最小值

我们可以只用np.inf填充第一行和最后一行不需要的数据。

df.loc["2011-01-07", :datetime.time(15, 0)] = np.inf
df.loc["2011-01-10", datetime.time(13, 30):] = np.inf

你得到

|            | 13:00:00 | 13:30:00 | 14:00:00 | 14:30:00 | 15:00:00 | 15:30:00 | 16:00:00 | 16:30:00 | 
|------------|----------|----------|----------|----------|----------|----------|----------|----------| 
| 2011-01-01 | 2054.0   | 2071.0   | 2060.0   | 2054.0   | 2042.0   | 2064.0   | 2043.0   | 2089.0   | 
| 2011-01-02 | 2096.0   | 2038.0   | 2079.0   | 2052.0   | 2056.0   | 2092.0   | 2007.0   | 2008.0   | 
| 2011-01-03 | 2002.0   | 2083.0   | 2077.0   | 2087.0   | 2097.0   | 2079.0   | 2046.0   | 2078.0   | 
| 2011-01-04 | 2011.0   | 2063.0   | 2014.0   | 2094.0   | 2052.0   | 2041.0   | 2026.0   | 2077.0   | 
| 2011-01-05 | 2045.0   | 2056.0   | 2001.0   | 2061.0   | 2061.0   | 2061.0   | 2094.0   | 2068.0   | 
| 2011-01-06 | 2035.0   | 2043.0   | 2069.0   | 2006.0   | 2066.0   | 2067.0   | 2021.0   | 2012.0   | 
| 2011-01-07 | inf      | inf      | inf      | inf      | inf      | 2010.0   | 2020.0   | 2016.0   | 
| 2011-01-08 | 2065.0   | 2025.0   | 2046.0   | 2024.0   | 2015.0   | 2011.0   | 2065.0   | 2013.0   | 
| 2011-01-09 | 2019.0   | 2036.0   | 2082.0   | 2009.0   | 2083.0   | 2009.0   | 2097.0   | 2046.0   | 
| 2011-01-10 | 2097.0   | inf      | inf      | inf      | inf      | inf      | inf      | inf      | 

为了得到结果:

df.loc["2011-01-07": "2011-01-10", :].idxmin(axis=1)

2011-01-07    15:30:00
2011-01-08    15:30:00
2011-01-09    14:30:00
2011-01-10    13:00:00
Freq: D, dtype: object

【讨论】:

有没有办法以矢量化方式处理数据框中的每一行? 矢量化时尚是什么意思?使用 pandas 构建函数? 所以我可以说从 3 天前 15:00 到 0 天前 11:30 获取每一行的最小值 你的答案会让我得到最后一行的正确答案。对于最后一行,前 3 行与当前行之间的最小值将是您提供的答案。但是,我希望对每一行执行此操作,因此对于每一行,我希望获取前 3 行到当前行的最小值。 您的意思是要计算每天从 3 天前 15:00 到相关当天 11:30 的最小值?然后我建议你使用 @JoeCondron 的答案和 for 循环【参考方案3】:

一种 hacky 方式,但应该很快,是连接移位的 DataFrame:

In [11]: df.shift(1)
Out[11]:
            13:00:00  13:30:00  14:00:00  14:30:00  15:00:00  15:30:00  16:00:00  16:30:00
2011-01-01       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN
2011-01-02      2054      2071      2060      2054      2042      2064      2043      2089
2011-01-03      2096      2038      2079      2052      2056      2092      2007      2008
2011-01-04      2002      2083      2077      2087      2097      2079      2046      2078
2011-01-05      2011      2063      2014      2094      2052      2041      2026      2077
2011-01-06      2045      2056      2001      2061      2061      2061      2094      2068
2011-01-07      2035      2043      2069      2006      2066      2067      2021      2012
2011-01-08      2031      2036      2057      2043      2098      2010      2020      2016
2011-01-09      2065      2025      2046      2024      2015      2011      2065      2013
2011-01-10      2019      2036      2082      2009      2083      2009      2097      2046

In [12]: df.shift(2).iloc[:, 4:]
Out[12]:
            15:00:00  15:30:00  16:00:00  16:30:00
2011-01-01       NaN       NaN       NaN       NaN
2011-01-02       NaN       NaN       NaN       NaN
2011-01-03      2042      2064      2043      2089
2011-01-04      2056      2092      2007      2008
2011-01-05      2097      2079      2046      2078
2011-01-06      2052      2041      2026      2077
2011-01-07      2061      2061      2094      2068
2011-01-08      2066      2067      2021      2012
2011-01-09      2098      2010      2020      2016
2011-01-10      2015      2011      2065      2013

In [13]: pd.concat([df.iloc[:, :1], df.shift(1), df.shift(2).iloc[:, 4:]], axis=1)
Out[13]:
            13:00:00  13:00:00  13:30:00  14:00:00  14:30:00  15:00:00  15:30:00  16:00:00  16:30:00  15:00:00  15:30:00  16:00:00  16:30:00
2011-01-01      2054       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN       NaN
2011-01-02      2096      2054      2071      2060      2054      2042      2064      2043      2089       NaN       NaN       NaN       NaN
2011-01-03      2002      2096      2038      2079      2052      2056      2092      2007      2008      2042      2064      2043      2089
2011-01-04      2011      2002      2083      2077      2087      2097      2079      2046      2078      2056      2092      2007      2008
2011-01-05      2045      2011      2063      2014      2094      2052      2041      2026      2077      2097      2079      2046      2078
2011-01-06      2035      2045      2056      2001      2061      2061      2061      2094      2068      2052      2041      2026      2077
2011-01-07      2031      2035      2043      2069      2006      2066      2067      2021      2012      2061      2061      2094      2068
2011-01-08      2065      2031      2036      2057      2043      2098      2010      2020      2016      2066      2067      2021      2012
2011-01-09      2019      2065      2025      2046      2024      2015      2011      2065      2013      2098      2010      2020      2016
2011-01-10      2097      2019      2036      2082      2009      2083      2009      2097      2046      2015      2011      2065      2013

并在各列中取最小值(确保丢弃在给定日期过早或过晚的列:

In [14]: pd.concat([df.iloc[:, :1], df.shift(1), df.shift(2).iloc[:, 4:]], axis=1).min(1)
Out[14]:
2011-01-01    2054
2011-01-02    2042
2011-01-03    2002
2011-01-04    2002
2011-01-05    2011
2011-01-06    2001
2011-01-07    2006
2011-01-08    2010
2011-01-09    2010
2011-01-10    2009
Freq: D, dtype: float64

通过在连接之前取每个移位的 DataFrame 的最小值,您可以更有效但更嘈杂:

In [21]: pd.concat([df.iloc[:, :1].min(1),
                    df.shift(1).min(1),
                    df.shift(2).iloc[:, 4:].min(1)],
                   axis=1).min(1)
Out[21]:
2011-01-01    2054
2011-01-02    2042
2011-01-03    2002
2011-01-04    2002
2011-01-05    2011
2011-01-06    2001
2011-01-07    2006
2011-01-08    2010
2011-01-09    2010
2011-01-10    2009
Freq: D, dtype: float64

两者都会比循环数天快得多。

【讨论】:

这是最接近我想要的,但我希望班次不要被硬编码。有时我想在 5 行中取最小值,有时在 2 行中取最小值。你知道这样的方法吗? @user1367204 把它放在一个函数中。 您的建议与我当前的执行方式以及我在问题描述中描述为当前解决方案的方式非常相似。你知道处理 pandas 的非函数式原生方式吗? @user1367204 不,不会有在第一天/最后一天切片某些列的条件。您可以尝试使用堆叠和rolling_min,但我不相信它会更快。最小值的连接(即我的最后一个代码块)应该更快并且更容易包装在一个函数中 - 只需使用列表理解。【参考方案4】:

我使用 pandas 的 stack() 方法和 timeseries 对象从样本数据构建结果。这种方法通过一些调整可以很好地推广到任意时间范围,并使用 pandas 内置的功能来构建结果。

import pandas as pd
import datetime as dt
# import df from json
df = pd.read_json('''"13:00:00":     "1293840000000":2085,"1293926400000":2062,"1294012800000":2035,"1294099200000":2086,"1294185600000":2006,"1294272000000":2097,"1294358400000":2078,"1294444800000":2055,"1294531200000":2023,"1294617600000":2024,
                      "13:30:00":"1293840000000":2045,"1293926400000":2039,"1294012800000":2035,"1294099200000":2045,"1294185600000":2025,"1294272000000":2099,"1294358400000":2028,"1294444800000":2028,"1294531200000":2034,"1294617600000":2010,
                      "14:00:00":"1293840000000":2095,"1293926400000":2006,"1294012800000":2001,"1294099200000":2032,"1294185600000":2022,"1294272000000":2040,"1294358400000":2024,"1294444800000":2070,"1294531200000":2081,"1294617600000":2095,
                      "14:30:00":"1293840000000":2057,"1293926400000":2042,"1294012800000":2018,"1294099200000":2023,"1294185600000":2025,"1294272000000":2016,"1294358400000":2066,"1294444800000":2041,"1294531200000":2098,"1294617600000":2023,
                      "15:00:00":"1293840000000":2082,"1293926400000":2025,"1294012800000":2040,"1294099200000":2061,"1294185600000":2013,"1294272000000":2063,"1294358400000":2024,"1294444800000":2036,"1294531200000":2096,"1294617600000":2068,
                      "15:30:00":"1293840000000":2090,"1293926400000":2084,"1294012800000":2092,"1294099200000":2003,"1294185600000":2001,"1294272000000":2049,"1294358400000":2066,"1294444800000":2082,"1294531200000":2090,"1294617600000":2005,
                      "16:00:00":"1293840000000":2081,"1293926400000":2003,"1294012800000":2009,"1294099200000":2001,"1294185600000":2011,"1294272000000":2098,"1294358400000":2051,"1294444800000":2092,"1294531200000":2029,"1294617600000":2073,
                      "16:30:00":"1293840000000":2015,"1293926400000":2095,"1294012800000":2094,"1294099200000":2042,"1294185600000":2061,"1294272000000":2006,"1294358400000":2042,"1294444800000":2004,"1294531200000":2099,"1294617600000":2088
                   '''#,convert_axes=False
                    )
date_idx=df.index                    
# stack the data 
stacked = df.stack()
# merge the multindex into a single idx. 
idx_list = stacked.index.tolist()
idx = []
for item in idx_list:
    day = item[0]
    time = item[1]
    idx += [dt.datetime(day.year, day.month, day.day, time.hour, time.minute)]
# make a time series to simplify slicing
timeseries = pd.TimeSeries(stacked.values, index=idx)
# get the results for each date

for i in range(2, len(date_idx)):
    # get the min values for each day in the sample data. 
    start_time='%s 15:00:00'%date_idx[i-2]
    end_time = '%s 13:30:00'%date_idx[i]
    slice_idx =timeseries.index>=start_time 
    slice_idx *= timeseries.index<=end_time
    print "%s %s"%(date_idx[i].date(), timeseries[slice_idx].min())

输出:

2011-01-03 2003
2011-01-04 2001
2011-01-05 2001
2011-01-06 2001
2011-01-07 2001
2011-01-08 2006
2011-01-09 2004
2011-01-10 2004

【讨论】:

以上是关于Pandas DataFrame:如何在行和列范围内获得最小值的主要内容,如果未能解决你的问题,请参考以下文章

如何从包含Python3中特定索引和列的列表的dict创建Pandas DataFrame?

pandas.DataFrame对行和列求和及添加新行和列

pandas:如何在行匹配查询后获取每n行的组?

pandas使用transpose函数对dataframe进行转置将dataframe的行和列进行互换(flip the rows and columns in dataframe)

Pandas获取DataFrame的行数和列数

Pandas Dataframe Multiindex 按级别和列值排序