【问题标题】:How to specify the number of rows a pandas dataframe will have?如何指定 pandas 数据框的行数?
【发布时间】:2015-06-18 23:41:50
【问题描述】:

我有一个 Pandas 数据框,并且每秒不断地添加一行数据,如下所示。

df.loc[time.strftime("%Y-%m-%d %H:%M:%S")] = [reading1, reading2, reading3]
>>>df
                     sensor1 sensor2 sensor3
2015-04-14 08:50:23    5.4     5.6     5.7
2015-04-14 08:50:24    5.5     5.6     5.8
2015-04-14 08:50:26    5.2     5.3     5.4

如果我继续这样做,最终我将开始遇到内存问题(每次它都会调用整个 DataFrame)。

我只需要保留 X 行数据。即在操作后,它将是:

>>>df
                     sensor1 sensor2 sensor3
(this row is gone)
2015-04-14 08:50:24    5.5     5.6     5.8
2015-04-14 08:50:26    5.2     5.3     5.4
2015-04-14 08:50:27    5.2     5.4     5.6

有没有一种方法可以指定最大行数,以便在添加任何后续行时,同时删除最旧的行而不用 “检查DataFrame的长度,如果DataFrame的长度> X,删除第一行,追加新行”?

像这样,但对于 Pandas DataFrame:https://stackoverflow.com/a/10155753/4783578

【问题讨论】:

  • 不清楚为什么你需要继续追加,但如果你想施加限制,为什么不检查len(df),一旦 len 达到限制就不要追加?
  • 你可以做的另一件事是在追加之后,如果长度大于你的限制,那么你只需分配一个切片,这样df = df.iloc[-500:] 会将最后 500 行分配回 df
  • 为什么不用新行替换旧行而不是追加?您可能每次都需要进行排序,但我认为这仍然会比扩展和缩小数据框的操作更快。和往常一样,一个带有代码的小示例数据框可以更容易地获得一个好的答案。

标签: python pandas dataframe data-analysis real-time-data


【解决方案1】:

pandas 将数据存储在数组中。执行您想要的那种操作本质上需要一个数组数据结构的副本。由于数据存储在连续(或跨步)内存中,因此在末尾添加某些内容并从开头删除某些内容需要将所有内容复制到新的内存区域。没有办法解决这个问题。您需要使用不同的数据结构。

编辑:再想一想,我看到了两种方法。

最简单和最直接的方法是使用 collections.deque 的元组。您可以在末尾追加一个新元组,如果它太满,它将从头开始转储相应的元组。最后,您可以将它们转换为DataFrame。我只是以for 循环为例,我认为您以不同的方式获取数据。没关系:

import pandas as pd
from collections import deque

maxlen = 1000

dq = deque(maxlen=maxlen)

for reading1, reading3, reading3 in readings:
    dq.append(pd.Series([reading1, reading2, reading3], 
                        index=['sensor1', 'sensor2', 'sensor3'], 
                        name=time.strftime("%Y-%m-%d %H:%M:%S")))

df = pd.concat(dq, axis=1).T

第二种方法是使用一个固定大小的DataFrame,并使用最大长度的模来选择要覆盖的地方,同时也保留DataFrame中的项目编号。然后您可以按项目编号排序。在您的情况下,您可以想象按时间排序,但这种方法更通用。与前面的示例一样,我将使用for 循环来演示,但您可能没有。此外,我还将假设您没有真正的可迭代对象 enumerate,如果您这样做了,那么您不必像我在这里那样跟踪索引号:

import pandas as pd

maxlen = 1000

df = pd.DataFrame(np.full((maxlen, 5), np.nan),
                  columns=['index', 'time', 
                           'sensor1', 'sensor2', 'sensor3'])

i = 0
for reading1, reading3, reading3 in readings:
    df.loc[i%maxlen, :] = [i, time.strftime("%Y-%m-%d %H:%M:%S"),
                           reading1, reading2, reading3]
    i+=1

df.sort('index', inplace=True)
del df['index']
df.set_index('time', drop=True, inplace=True)

【讨论】:

  • 我猜,我是反过来理解的。假设您有 10 行,当有 1 行进入时,您将其从顶部删除并在底部添加这个新行。数据框的大小始终保持 10。因此,收回我的解决方案。感谢您的澄清。
  • @TheBlackCat 你会建议什么不同的数据结构?不一定非得是 pandas,pandas 似乎只是便于分析:df.plot()to_csv
【解决方案2】:

这个例子初始化一个等于最大大小的 DataFrame 并用 None 填充它。然后它遍历新行列表,首先移动原始 DataFrame,然后将新行附加到末尾。您没有指定要如何处理索引,所以我忽略了它。

max_rows = 5
cols = list('AB')

# Initialize empty DataFrame
df = pd.DataFrame({c: np.repeat([None], [max_rows]) for c in cols})

new_rows = [pd.DataFrame({'A': [1], 'B': [10]}), 
            pd.DataFrame({'A': [2], 'B': [11]}),
            pd.DataFrame({'A': [3], 'B': [12]}),
            pd.DataFrame({'A': [4], 'B': [13]}),
            pd.DataFrame({'A': [5], 'B': [14]}),
            pd.DataFrame({'A': [6], 'B': [15]}),
            pd.DataFrame({'A': [7], 'B': [16]})]

for row in new_rows:
    df = df.shift(-1)
    df.iloc[-1, :] = row.values

>>> df
df
   A   B
0  3  12
1  4  13
2  5  14
3  6  15
4  7  16

让我们以 AAPL 一年的股票价格为例。

from datetime import timedelta

aapl = DataReader("AAPL", data_source="yahoo", start="2014-1-1", end="2015-1-1")
cols = aapl.columns
df = pd.DataFrame({c: np.repeat([None], [max_rows]) for c in aapl.columns})[cols]
# Initialize a datetime index
df.index = pd.DatetimeIndex(end=aapl.index[0] + timedelta(days=-1), periods=max_rows, freq='D')

for timestamp, row in aapl.iterrows():
    df = df.shift(-1)
    df.iloc[-1, :] = row.values
    idx = df.index[:-1].tolist()
    idx.append(timestamp)
    df.index = idx

>>> df
              Open    High     Low   Close       Volume Adj Close
2013-12-28  112.58  112.71  112.01  112.01  1.44796e+07    111.57
2013-12-29   112.1  114.52  112.01  113.99   3.3721e+07    113.54
2013-12-30  113.79  114.77   113.7  113.91  2.75989e+07    113.46
2013-12-31  113.64  113.92  112.11  112.52  2.98815e+07    112.08
2014-12-31  112.82  113.13  110.21  110.38  4.14034e+07    109.95

【讨论】:

  • 原始问题的最佳答案,如果我使用时间戳进行索引,有没有办法让它工作? >>> df = df.tail(maxlen) 为我工作
【解决方案3】:

一种方法是预先分配行,并循环替换值。

# Say we to limit to a thousand rows
N = 1000

# Create the DataFrame with N rows and 5 columns -- all NaNs
data = pd.DataFrame(pd.np.empty((N, 5)) * pd.np.nan) 

# To check the length of the DataFrame, we'll need to .dropna().
len(data.dropna())              # Returns 0

# Keep a running counter of the next index to insert into
counter = 0

# Insertion always happens at that counter
data.loc[counter, :] = pd.np.random.rand(5)

# ... and increment the counter, but when it exceeds N, set it to 0
counter = (counter + 1) % N

# Now, the DataFrame contains one row
len(data.dropna())              # Returns 1

# We can add several rows one after another. Let's add twice as many as N
for row in pd.np.random.rand(2 * N, 5):
    data.loc[counter, :] = row
    counter = (counter + 1) % N

# Now that we added them, we still have only the last N rows
len(data)                       # Returns N

这避免了以任何方式修改数据的需要,并且是附加数据的快速方法。但是,在以下情况下读取数据可能会更慢:

  • 数据的顺序很重要。如果您需要相同顺序的数据,则需要使用counterdata 进行切片以提取原始顺序。
  • 行数少。如果最终追加的行数少于 N,则需要 .dropna()(或计算插入的总行数)来删除未使用的行。

在我处理的大多数情况下,截断附加性能很重要,以上都不是真的,但你的情况可能会有所不同。在这种情况下,@Alexander 有一个很好的解决方案,涉及到.shift()

【讨论】:

    猜你喜欢
    • 2023-01-28
    • 1970-01-01
    • 2017-12-26
    • 2020-08-24
    • 2022-01-26
    • 1970-01-01
    • 2023-03-15
    • 2015-01-19
    • 2021-10-30
    相关资源
    最近更新 更多