尽管 DataStore 与 pandas 高度兼容,但两者之间仍有一些重要差异需要了解。
摘要表
| Aspect | pandas | DataStore |
|---|---|---|
| 执行 | 立即 | 惰性 |
| 返回类型 | DataFrame/Series | DataStore/ColumnExpr |
| 行顺序 | 保持不变 | 保持不变 (自动) ;在性能模式下不作保证 |
| inplace | 支持 | 不支持 |
| 索引 | 完整支持 | 简化 |
| 内存 | 所有数据都在内存中 | 数据保留在源端 |
1. 惰性与立即执行
pandas (立即)
操作会立即执行:
import pandas as pd
df = pd.read_csv("data.csv") # Loads entire file NOW
result = df[df['age'] > 25] # Filters NOW
grouped = result.groupby('city')['salary'].mean() # Aggregates NOWDataStore (惰性)
仅在需要结果时才会执行操作:
from chdb import datastore as pd
ds = pd.read_csv("data.csv") # Just records the source
result = ds[ds['age'] > 25] # Just records the filter
grouped = result.groupby('city')['salary'].mean() # Just records
# Execution happens here:
print(grouped) # Executes when displaying
df = grouped.to_df() # Or when converting to pandas为什么这很重要
惰性执行可带来:
- 查询优化:多个操作会编译成一条 SQL 查询
- 列裁剪:仅读取所需的列
- 过滤器下推:过滤器会在源端生效
- 内存效率:避免加载不需要的数据
2. 返回类型
pandas
df['col'] # Returns pd.Series
df[['a', 'b']] # Returns pd.DataFrame
df[df['x'] > 10] # Returns pd.DataFrame
df.groupby('x') # Returns DataFrameGroupByDataStore
ds['col'] # Returns ColumnExpr (lazy)
ds[['a', 'b']] # Returns DataStore (lazy)
ds[ds['x'] > 10] # Returns DataStore (lazy)
ds.groupby('x') # Returns LazyGroupBy转换为 pandas 数据类型
# Get pandas DataFrame
df = ds.to_df()
df = ds.to_pandas()
# Get pandas Series from column
series = ds['col'].to_pandas()
# Or trigger execution
print(ds) # Automatically converts for display3. 执行触发器
DataStore 会在需要实际值时执行:
| 触发器 | 示例 | 说明 |
|---|---|---|
print() / repr() |
print(ds) |
显示时需要数据 |
len() |
len(ds) |
需要行数 |
.columns |
ds.columns |
需要列名 |
.dtypes |
ds.dtypes |
需要类型信息 |
.shape |
ds.shape |
需要维度信息 |
.values |
ds.values |
需要实际数据 |
.index |
ds.index |
需要索引 |
to_df() |
ds.to_df() |
显式转换 |
| Iteration | for row in ds |
需要进行迭代 |
equals() |
ds.equals(other) |
需要比较 |
保持惰性执行的操作
| 操作 | 返回值 |
|---|---|
filter() |
DataStore |
select() |
DataStore |
sort() |
DataStore |
groupby() |
LazyGroupBy |
join() |
DataStore |
ds['col'] |
ColumnExpr |
ds[['a', 'b']] |
DataStore |
ds[condition] |
DataStore |
4. 行的顺序
pandas
始终保留行顺序:
df = pd.read_csv("data.csv")
print(df.head()) # Always same order as fileDataStore
对于大多数操作,会自动保留行顺序:
ds = pd.read_csv("data.csv")
print(ds.head()) # Matches file order
# Filter preserves order
ds_filtered = ds[ds['age'] > 25] # Same order as pandasDataStore 会在内部自动记录原始行的位置 (使用 rowNumberInAllBlocks()) ,以确保顺序与 pandas 保持一致。
何时会保留行顺序
- 文件源 (CSV、Parquet、JSON 等)
- pandas DataFrame 源
- 过滤操作
- 列选择
- 显式调用
sort()或sort_values()之后 - 会确定顺序的操作 (
nlargest()、nsmallest()、head()、tail())
顺序何时可能发生变化
- 在
groupby()聚合之后 (使用sort_values()可确保顺序一致) - 使用某些 join type 进行
merge()/join()之后 - 在 性能模式 (
config.use_performance_mode()) 下:任何操作的行顺序都无法保证。请参见性能模式。
5. 没有 inplace 参数
pandas
df.drop(columns=['col'], inplace=True) # Modifies df
df.fillna(0, inplace=True) # Modifies df
df.rename(columns={'old': 'new'}, inplace=True)DataStore
不支持 inplace=True。请始终将结果赋值给变量:
ds = ds.drop(columns=['col']) # Returns new DataStore
ds = ds.fillna(0) # Returns new DataStore
ds = ds.rename(columns={'old': 'new'}) # Returns new DataStore为什么没有 inplace?
DataStore 使用不可变操作,以便实现:
- 查询构建 (惰性求值)
- 线程安全
- 更容易调试
- 更简洁的代码
6. 索引支持
pandas
完整的索引支持:
df = df.set_index('id')
df.loc['user123'] # Label-based access
df.loc['a':'z'] # Label-based slicing
df.reset_index()
df.index.name = 'user_id'DataStore
简化了对索引的支持:
# Basic operations work
ds.loc[0:10] # Integer position
ds.iloc[0:10] # Same as loc for DataStore
# For pandas-style index operations, convert first
df = ds.to_df()
df = df.set_index('id')
df.loc['user123']DataStore 的数据来源很重要
- DataFrame 来源:保留 pandas 索引
- File 来源:使用简单的整数索引
7. 比较行为
与 pandas 对比
pandas 无法识别 DataStore 对象:
import pandas as pd
from chdb import datastore as ds
pdf = pd.DataFrame({'a': [1, 2, 3]})
dsf = ds.DataFrame({'a': [1, 2, 3]})
# This doesn't work as expected
pdf == dsf # pandas doesn't know DataStore
# Solution: convert DataStore to pandas
pdf.equals(dsf.to_pandas()) # True使用 equals()
# DataStore.equals() also works
dsf.equals(pdf) # Compares with pandas DataFrame8. 类型推断
pandas
使用 numpy/pandas 数据类型:
df['col'].dtype # int64, float64, object, datetime64, etc.DataStore
可使用 ClickHouse 类型:
ds['col'].dtype # Int64, Float64, String, DateTime, etc.
# Types are converted when going to pandas
df = ds.to_df()
df['col'].dtype # Now pandas type显式类型转换
# Force specific type
ds['col'] = ds['col'].astype('int64')9. 内存模型
pandas
所有数据都存储在内存中:
df = pd.read_csv("huge.csv") # 10GB in memory!DataStore
数据会一直保留在源端,直到需要时才使用:
ds = pd.read_csv("huge.csv") # Just metadata
ds = ds.filter(ds['year'] == 2024) # Still just metadata
# Only filtered result is loaded
df = ds.to_df() # Maybe only 1GB now10. 错误信息
不同的错误来源
- pandas 错误:来自 pandas 库
- DataStore 错误:来自 chDB 或 ClickHouse
# May see ClickHouse-style errors
# "Code: 62. DB::Exception: Syntax error..."调试技巧
# View the SQL to debug
print(ds.to_sql())
# See execution plan
ds.explain()
# Enable debug logging
from chdb.datastore.config import config
config.enable_debug()迁移清单
从 pandas 迁移时:
- 修改 import 语句
- 移除
inplace=True参数 - 在需要 pandas DataFrame 的地方显式添加
to_df() - 如果行顺序很重要,请添加排序
- 使用
to_pandas()进行对比测试 - 使用具有代表性的数据规模进行测试
快速参考
| pandas | DataStore |
|---|---|
df[condition] |
相同 (返回 DataStore) |
df.groupby() |
相同 (返回 LazyGroupBy) |
df.drop(inplace=True) |
ds = ds.drop() |
df.equals(other) |
ds.to_pandas().equals(other) |
df.loc['label'] |
ds.to_df().loc['label'] |
print(df) |
相同 (会触发执行) |
len(df) |
相同 (会触发执行) |