如何高效实现大型CSV数据的三维空间块元素统计?
问题描述
我有一个大型CSV数据集,结构如下:
id,x,y,z 34295,695.117,74.0177,70.6486 20915,800.784,98.5225,19.3014 30369,870.428,98.742,23.9953 48151,547.681,53.055,174.176 34026,1231.02,73.7678,203.404 34797,782.725,73.9831,218.592 15598,983.502,82.9373,314.081 34076,614.738,86.3301,171.316 20328,889.016,98.9201,13.3068 ...
我希望将空间划分为x、y、z范围的三维块,统计每个块内的元素数量。例如划分为100×100×100的立方体时,
counts[900][100][100] = 3
因为示例中id为20915、30369、20328的数据都处于x=800-900、y=0-100、z=0-100的范围内。
目前我通过构建多层字典的暴力法实现,但处理实际大型CSV时速度极慢:
import numpy import pandas df = pandas.read_csv("test.csv") xs = numpy.linspace(0, 1300, 14, endpoint=True) ys = numpy.linspace(0, 1000, 11, endpoint=True) zs = numpy.linspace(0, 1000, 11, endpoint=True) c = {} for x_index, x in enumerate(xs[:-1]): c[xs[x_index + 1]] = {} for y_index, y in enumerate(ys[:-1]): c[xs[x_index + 1]][ys[y_index + 1]] = {} for z_index, z in enumerate(zs[:-1]): c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]] = df[(df["x"] > xs[x_index]) & (df["x"] <= xs[x_index + 1]) & (df["y"] > ys[y_index]) & (df["y"] <= ys[y_index + 1]) & (df["z"] > zs[z_index]) & (df["z"] <= zs[z_index + 1])]["id"].count() if (c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]] > 0): print("c[" + str(xs[x_index + 1]) + "][" + str(ys[y_index + 1]) + "][" + str(zs[z_index + 1]) + "] = " + str(c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]]))
请问有什么更高效简洁的实现方案?
高效解决方案
你的暴力法效率低的核心原因是三重循环中每次都对全量数据做布尔过滤,时间复杂度为O(N*K)(N是数据量,K是三维块总数),对于大型数据集会非常耗时。以下两种方案可以将时间复杂度降至O(N),大幅提升效率:
方案一:使用numpy的histogramdd(推荐)
histogramdd是numpy专门为多维直方图设计的函数,直接统计各区间内的样本数量,底层用C实现,速度极快:
import numpy as np import pandas as pd # 读取数据 df = pd.read_csv("test.csv") # 定义各维度的区间边界(与原代码一致) x_bins = np.linspace(0, 1300, 14, endpoint=True) y_bins = np.linspace(0, 1000, 11, endpoint=True) z_bins = np.linspace(0, 1000, 11, endpoint=True) # 提取x、y、z列转为numpy数组 points = df[["x", "y", "z"]].values # 计算三维直方图:counts是三维数组,edges是各维度的边界数组 counts, edges = np.histogramdd(points, bins=(x_bins, y_bins, z_bins)) # 输出非零计数的块(和原代码输出格式一致) for x_idx, x_upper in enumerate(x_bins[1:]): for y_idx, y_upper in enumerate(y_bins[1:]): for z_idx, z_upper in enumerate(z_bins[1:]): cnt = counts[x_idx, y_idx, z_idx] if cnt > 0: print(f"c[{x_upper}][{y_upper}][{z_upper}] = {int(cnt)}")
优势
- 速度远超三重循环,处理百万级数据也能秒级完成
- 返回的三维数组
counts可直接用于后续分析或可视化 - 自动处理区间边界,无需手动编写复杂的布尔过滤条件
方案二:使用pandas的cut+groupby
先通过cut给每个数据点标记所属区间的上边界,再通过groupby分组计数,逻辑清晰且保留pandas的数据框格式:
import pandas as pd import numpy as np df = pd.read_csv("test.csv") # 定义区间边界 x_bins = np.linspace(0, 1300, 14, endpoint=True) y_bins = np.linspace(0, 1000, 11, endpoint=True) z_bins = np.linspace(0, 1000, 11, endpoint=True) # 为每个数据点分配对应的区间上边界标签 df["x_bin"] = pd.cut(df["x"], bins=x_bins, labels=x_bins[1:], include_lowest=True) df["y_bin"] = pd.cut(df["y"], bins=y_bins, labels=y_bins[1:], include_lowest=True) df["z_bin"] = pd.cut(df["z"], bins=z_bins, labels=z_bins[1:], include_lowest=True) # 按三个区间标签分组,统计每组数量 counts_df = df.groupby(["x_bin", "y_bin", "z_bin"]).size().reset_index(name="count") # 输出非零计数结果 for _, row in counts_df.iterrows(): print(f"c[{row['x_bin']}][{row['y_bin']}][{row['z_bin']}] = {row['count']}") # 若需要转为字典结构(类似原代码的多层字典),可以用: count_dict = counts_df.set_index(["x_bin", "y_bin", "z_bin"])["count"].to_dict() # 访问示例:count_dict[(900.0, 100.0, 100.0)]
优势
- 逻辑直观,易于理解和扩展
- 保留pandas数据框格式,方便后续进行筛选、排序等操作
- 仅返回有数据的区间,无需遍历所有空块
内容的提问来源于stack exchange,提问作者Troy Rockwood
相关产品推荐
相关产品推荐

