将pandas数据框拆分成大致相同的块。

3
我想将这个DataFrame分成预先定义的数量的块,并且每个块的大小大致相同
import pandas as pd

df = pd.DataFrame({
    "user": ["A", "A", "B", "C", "C", "C"],
    "value": [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]})


#     user  value
# 0      A    0.3
# 1      A    0.4
# 2      B    0.5
# 3      C    0.6
# 4      C    0.7
# 5      C    0.8

由于DataFrame非常大(数百万行),因此代码应该更有效率。

问题在于某些用户只应存在于其中一个块中。

例如,如果块数为3,则:

  • 第一块应具有行[0, 1]
  • 第二块应具有第2行,而不应具有第3行,因为第3行是给用户C的
  • 第三块应具有行[3, 4, 5]
# Chunk #1 (DataFrame):
# 0      A    0.3
# 1      A    0.4

# Chunk #2 (DataFrame):
# 2      B    0.5

# Chunk #3 (DataFrame):
# 3      C    0.6
# 4      C    0.7
# 5      C    0.8

将这个分成三块的过程是不正确的,因为用户C会出现在两个块中:

# Chunk #1 (DataFrame):
# 0      A    0.3
# 1      A    0.4

# Chunk #2 (DataFrame):
# 2      B    0.5
# 3      C    0.6

# Chunk #3 (DataFrame):
# 4      C    0.7
# 5      C    0.8

我认为,将DataFrameGroupBy按照用户进行groupby后再将其分成块的一些解决方案即可。


请问您能否添加您期望的输出样式?它应该是按用户分割的数据帧列表吗? - sophocles
当n=2时会发生什么? - anky
@sophocles,感谢您的评论!它应该是一个大小大致相同的DataFrame列表,但一个用户只能在一个结果DataFrame中。 - Konstantin
1
@anky,对于这个问题我比较灵活,[[0,1,2],[3,4,5]]和[[0,1],[2,3,4,5]]两种情况都可以满足要求。当然,有些用户可能只有两个值,有些用户可能有数百个值。 - Konstantin
你需要保持顺序吗?例如,你可以使用 groupby("user") 将数据分成组,然后合并这些组以达到所需的最终块数。这种方法可能会将在原始数据框中不相邻的用户分组在一起,这样做是否可以? - filippo
@filippo,这应该没问题! - Konstantin
3个回答

2
您可以将我们的“user”列转换为分类列,并使用“qcut”进行均匀高度分箱。不幸的是,“qcut”无法为不连续分布找到唯一的分箱边缘,因此如果一个用户被过度表示,您可能会遇到一些问题。您可以使用“duplicates =“ drop””,但您请求的某些箱子将聚集在一起,因此您不总是拥有所需的数量的箱子。
猜想您将不得不编写一些算法来进行适当的重新分箱,无法立即找到任何现成的解决方案。
这里是一个使用“pd.qcut”的示例。
让我们构建一个虚拟数据集。
user = np.random.choice(["A", "B", "C", "D", "E", "F", "G", "H"], 10000)
value = np.random.random(size=user.shape)
df = pd.DataFrame({"user": user, "value": value})
print(df.user.value_counts())

E    1329
C    1281
G    1277
F    1260
H    1231
D    1223
A    1205
B    1194
Name: user, dtype: int64

为每个独特的用户分配一个整数代码,并使用qcut重新划分。
codes = df.user.astype("category").cat.codes    
nbins = 3
df["bin"] = pd.qcut(codes, nbins, labels=False)
df.groupby("user").bin.value_counts()

让我们来检查一下结果

print(df.bin.value_counts())
1    3788
0    3629
2    2583
Name: bin, dtype: int64

print(df.groupby("user").bin.value_counts())
user  bin
A     0      1266
B     0      1158
C     0      1205
D     1      1255
E     1      1246
F     1      1287
G     2      1274
H     2      1309
Name: bin, dtype: int64

1
这是一份精彩的配方 - 纯金 - 非常感谢你! - jtlz2

1
这是否足够?
df_grouped = df.groupby('user')
df_list = [df for user, df in df_grouped]

Out[1352]: 
[  user  value
 0    A    0.3
 1    A    0.4,
   user  value
 2    B    0.5,
   user  value
 3    C    0.6
 4    C    0.7
 5    C    0.8]

这在我的机器上运行相对较快:

>>> df.shape
(7200000, 2)

>>> print(end - start)
0.532534122467041

1
或者是这样:df_list = {user:df for user, df in df_grouped} - Sin4wd
@sophocles 谢谢您的回复,但我在这个解决方案中没有看到块的数量。我需要一个大致相同大小的DataFrame列表。每个DataFrame可能有多个用户,但是不应该将任何单个用户的值放入多个DataFrame中。 - Konstantin
这只回答了问题的一部分,您需要按用户拆分数据框并重新分组为大致相同大小的块,至少就我理解的而言。 - filippo
1
抱歉,我现在无法处理这些更改。我必须将更改推迟到以后或明天。谢谢大家。 - sophocles

1
你也可以尝试使用np.split,通过检查块是否与用户中的元素数量相同来设置一些条件,然后在用户中拆分;否则,保留列表中的前n个用户进行拆分:
def split_fun(data,n):
    cond = len(set(data['user'])) == n
    f = data['user'].factorize()[0]+1
    if cond:
        p = np.where(np.diff(f)>0)[0]+1
    else:
        p= np.where(np.diff((f>n).view('i1'))>0)[0]+1
    return np.split(data,p)

样例运行:

split_fun(df,2)
[  user  value
 0    A    0.3
 1    A    0.4
 2    B    0.5,
   user  value
 3    C    0.6
 4    C    0.7
 5    C    0.8]

split_fun(df,3)

[  user  value
 0    A    0.3
 1    A    0.4,
   user  value
 2    B    0.5,
   user  value
 3    C    0.6
 4    C    0.7
 5    C    0.8]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接