簡體   English   中英

如何有效地迭代 Pandas dataframe 的連續塊

[英]How to iterate over consecutive chunks of Pandas dataframe efficiently

我有一個大的 dataframe(幾百萬行)。

我希望能夠對其進行 groupby 操作,但只是按任意連續的(最好是大小相等的)行子集進行分組,而不是使用各個行的任何特定屬性來決定它們 go 到哪個組。

用例:我想通過 IPython 中的並行 map 將 function 應用於每一行。 go 的哪些行到哪個后端引擎並不重要,因為 function 一次基於一行計算結果。 (至少在概念上是這樣;實際上它是矢量化的。)

我想出了這樣的事情:

# Generate a number from 0-9 for each row, indicating which tenth of the DF it belongs to
max_idx = dataframe.index.max()
tenths = ((10 * dataframe.index) / (1 + max_idx)).astype(np.uint32)

# Use this value to perform a groupby, yielding 10 consecutive chunks
groups = [g[1] for g in dataframe.groupby(tenths)]

# Process chunks in parallel
results = dview.map_sync(my_function, groups)

但這似乎很啰嗦,並且不能保證大小相同的塊。 特別是如果索引是稀疏的或非整數的或其他什么。

對更好的方法有什么建議嗎?

謝謝!

使用 numpy 的 array_split():

import numpy as np
import pandas as pd

data = pd.DataFrame(np.random.rand(10, 3))
for chunk in np.array_split(data, 5):
  assert len(chunk) == len(data) / 5, "This assert may fail for the last chunk if data lenght isn't divisible by 5"

我不確定這是否正是您想要的,但我發現另一個 SO 線程上的這些 grouper 函數對於執行多處理器池非常有用。

這是該線程中的一個簡短示例,它可能會執行您想要的操作:

import numpy as np
import pandas as pds

df = pds.DataFrame(np.random.rand(14,4), columns=['a', 'b', 'c', 'd'])

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in xrange(0, len(seq), size))

for i in chunker(df,5):
    print i

這給了你這樣的東西:

          a         b         c         d
0  0.860574  0.059326  0.339192  0.786399
1  0.029196  0.395613  0.524240  0.380265
2  0.235759  0.164282  0.350042  0.877004
3  0.545394  0.881960  0.994079  0.721279
4  0.584504  0.648308  0.655147  0.511390
          a         b         c         d
5  0.276160  0.982803  0.451825  0.845363
6  0.728453  0.246870  0.515770  0.343479
7  0.971947  0.278430  0.006910  0.888512
8  0.044888  0.875791  0.842361  0.890675
9  0.200563  0.246080  0.333202  0.574488
           a         b         c         d
10  0.971125  0.106790  0.274001  0.960579
11  0.722224  0.575325  0.465267  0.258976
12  0.574039  0.258625  0.469209  0.886768
13  0.915423  0.713076  0.073338  0.622967

我希望這有幫助。

編輯

在這種情況下,我以(大約)這種方式將此函數與處理器池一起使用:

from multiprocessing import Pool

nprocs = 4

pool = Pool(nprocs)

for chunk in chunker(df, nprocs):
    data = pool.map(myfunction, chunk)
    data.domorestuff()

我認為這應該與使用 IPython 分布式機器非常相似,但我還沒有嘗試過。

實際上,您不能保證大小相等的塊。 行數 (N) 可能是質數,在這種情況下,您只能在 1 或 N 處獲得相同大小的塊。因此,現實世界的分塊通常使用固定大小並在最后允許較小的塊。 我傾向於將數組傳遞給groupby 從...開始:

>>> df = pd.DataFrame(np.random.rand(15, 5), index=[0]*15)
>>> df[0] = range(15)
>>> df
    0         1         2         3         4
0   0  0.746300  0.346277  0.220362  0.172680
0   1  0.657324  0.687169  0.384196  0.214118
0   2  0.016062  0.858784  0.236364  0.963389
[...]
0  13  0.510273  0.051608  0.230402  0.756921
0  14  0.950544  0.576539  0.642602  0.907850

[15 rows x 5 columns]

我故意通過將索引設置為 0 來使索引沒有信息,我們只需決定我們的大小(這里是 10)並將數組除以它:

>>> df.groupby(np.arange(len(df))//10)
<pandas.core.groupby.DataFrameGroupBy object at 0xb208492c>
>>> for k,g in df.groupby(np.arange(len(df))//10):
...     print(k,g)
...     
0    0         1         2         3         4
0  0  0.746300  0.346277  0.220362  0.172680
0  1  0.657324  0.687169  0.384196  0.214118
0  2  0.016062  0.858784  0.236364  0.963389
[...]
0  8  0.241049  0.246149  0.241935  0.563428
0  9  0.493819  0.918858  0.193236  0.266257

[10 rows x 5 columns]
1     0         1         2         3         4
0  10  0.037693  0.370789  0.369117  0.401041
0  11  0.721843  0.862295  0.671733  0.605006
[...]
0  14  0.950544  0.576539  0.642602  0.907850

[5 rows x 5 columns]

當索引與其不兼容時,基於​​切片 DataFrame 的方法可能會失敗,盡管您始終可以使用.iloc[a:b]來忽略索引值並按位置訪問數據。

一個好的環境的標志是有很多選擇,所以我會從Anaconda Blaze 中添加這個,真正使用Odo

import blaze as bz
import pandas as pd

df = pd.DataFrame({'col1':[1,2,3,4,5], 'col2':[2,4,6,8,10]})

for chunk in bz.odo(df, target=bz.chunks(pd.DataFrame), chunksize=2):
    # Do stuff with chunked dataframe

用於迭代熊貓數據幀和系列的生成器函數

塊函數的生成器版本如下所示。 此外,此版本適用於 pd.DataFrame 或 pd.Series 的自定義索引(例如浮點型索引)

    import numpy as np
    import pandas as pd

    df_sz = 14

    df = pd.DataFrame(np.random.rand(df_sz,4), 
                      index=np.linspace(0., 10., num=df_sz),
                      columns=['a', 'b', 'c', 'd']
                     )

    def chunker(seq, size):
        for pos in range(0, len(seq), size):
            yield seq.iloc[pos:pos + size] 

    chunk_size = 6
    for i in chunker(df, chunk_size):
        print(i)

   chnk = chunker(df, chunk_size)
   print('\n', chnk)
   print(next(chnk))
   print(next(chnk))
   print(next(chnk))

輸出是

a         b         c         d
0.000000  0.560627  0.665897  0.683055  0.611884
0.769231  0.241871  0.357080  0.841945  0.340778
1.538462  0.065009  0.234621  0.250644  0.552410
2.307692  0.431394  0.235463  0.755084  0.114852
3.076923  0.173748  0.189739  0.148856  0.031171
3.846154  0.772352  0.697762  0.557806  0.254476
                 a         b         c         d
4.615385  0.901200  0.977844  0.250316  0.957408
5.384615  0.400939  0.520841  0.863015  0.177043
6.153846  0.356927  0.344220  0.863067  0.400573
6.923077  0.375417  0.156420  0.897889  0.810083
7.692308  0.666371  0.152800  0.482446  0.955556
8.461538  0.242711  0.421591  0.005223  0.200596
                  a         b         c         d
9.230769   0.735748  0.402639  0.527825  0.595952
10.000000  0.420209  0.365231  0.966829  0.514409

- generator object chunker at 0x7f503c9d0ba0

First "next()":
                 a         b         c         d
0.000000  0.560627  0.665897  0.683055  0.611884
0.769231  0.241871  0.357080  0.841945  0.340778
1.538462  0.065009  0.234621  0.250644  0.552410
2.307692  0.431394  0.235463  0.755084  0.114852
3.076923  0.173748  0.189739  0.148856  0.031171
3.846154  0.772352  0.697762  0.557806  0.254476

Second "next()":
                 a         b         c         d
4.615385  0.901200  0.977844  0.250316  0.957408
5.384615  0.400939  0.520841  0.863015  0.177043
6.153846  0.356927  0.344220  0.863067  0.400573
6.923077  0.375417  0.156420  0.897889  0.810083
7.692308  0.666371  0.152800  0.482446  0.955556
8.461538  0.242711  0.421591  0.005223  0.200596

Third "next()":
                  a         b         c         d
9.230769   0.735748  0.402639  0.527825  0.595952
10.000000  0.420209  0.365231  0.966829  0.514409
import pandas as pd

def batch(iterable, batch_number=10):
    """
    split an iterable into mini batch with batch length of batch_number
    supports batch of a pandas dataframe
    usage:
        for i in batch([1,2,3,4,5], batch_number=2):
            print(i)
        
        for idx, mini_data in enumerate(batch(df, batch_number=10)):
            print(idx)
            print(mini_data)
    """
    l = len(iterable)

    for idx in range(0, l, batch_number):
        if isinstance(iterable, pd.DataFrame):
            # dataframe can't split index label, should iter according index
            yield iterable.iloc[idx:min(idx+batch_number, l)]
        else:
            yield iterable[idx:min(idx+batch_number, l)]

另一種方法..

# .. load df ..

CHUNK_SIZE = 100000

for chunk_num in range(len(df) // CHUNK_SIZE + 1):
    start_index = chunk_num*CHUNK_SIZE
    end_index = min(chunk_num*CHUNK_SIZE + CHUNK_SIZE, len(df))
    chunk = df[start_index:end_index]

    # .. do calculaton on chunk here ..

您使用groupby的建議非常好,但您應該使用np.arange(len(dataframe)) // batch_size而不是dataframe.index ,因為索引可以是非整數和非連續的。

我對給出的答案進行了一些基准測試 投票最高的那個速度非常慢。 請考慮使用公認的解決方案:

data.groupby(np.arange(len(dataframe)) // batch_size)

基准

基准代碼:

import numpy as np
import pandas as pd
import time
from tqdm.auto import tqdm

#@markdown # Create a properly funcky `pd.DataFrame`
data = pd.DataFrame([
  {
      'x': np.random.randint(23515243),
      'y': 364274*np.random.rand()-134562,
      'z': ''.join(np.random.choice(list('`1234567890-=qwertyuiop[]\asdfghjkl;\'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?'), np.random.randint(54,89), replace=True)),
  }
  for _ in tqdm(range(22378))
])
data.index = ['a'] * len(data)

data = pd.concat([data] * 100)

batch_size = 64

times = []

t0 = time.time()
for chunk in np.array_split(data, (len(data) + batch_size - 1) // batch_size):
  pass
times.append({'method': 'np.array_split', 'time': -t0 + time.time()})

t0 = time.time()
for _, chunk in data.groupby(np.arange(len(data)) // batch_size):
  pass
times.append({'method': 'groupby', 'time': -t0 + time.time()})

def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
  
t0 = time.time()
for chunk in chunker(data, batch_size):
  pass
times.append({'method': '[]-syntax', 'time': -t0 + time.time()})

# t0 = time.time()
# for chunk in bz.odo(data, target=bz.chunks(pd.DataFrame), chunksize=batch_size):
#   pass
# times.append({'method': 'bz.odo', 'time': -t0 + time.time()})


def chunker(seq, size):
    for pos in range(0, len(seq), size):
        yield seq.iloc[pos:pos + size] 

t0 = time.time()
for i in chunker(data, batch_size):
    pass
times.append({'method': '.iloc[]-syntax', 'time': -t0 + time.time()})

pd.DataFrame(times)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM