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
的结果是 Series
和 MultiIndex
,其中内部级别由原始列组成。然后,我们使用 tuple
对与开始和结束日期和时间进行切片。
如果您要进行大量此类操作,那么您应该考虑将df.stack()
分配给某个变量。然后您可能会考虑将索引更改为正确的DatetimeIndex
。然后,您可以根据需要使用时间序列和网格格式。
这是另一种避免堆叠的方法,并且在您实际使用的大小的 DataFrame 上速度更快(作为一次性的;切片堆叠的DataFrame
在堆叠后会快得多,所以如果您是执行其中许多操作,您应该堆叠并转换索引)。
它不太通用,因为它适用于min
和max
,但不适用于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_row
则middle_min
是nan
,但只要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使用transpose函数对dataframe进行转置将dataframe的行和列进行互换(flip the rows and columns in dataframe)