跳转至

Pandas 支持#

gpdatacached.extensions.pandas_support 让 pandas 的 SeriesDataFrame 可以作为 GPDC 对象被缓存,底层由活跃的 SeriesObject / DataFrameObject 包装类支撑。

pandas 是可选依赖 —— 请使用 pandas 扩展进行安装:

pip install "gpdatacached[pandas]"

设计理念#

gpdatacached 是一个跨进程共享的缓存,并非进程内的 pandas 工作区。其预期使用模式是:

一个写入者把大型结构缓存起来;多个读取进程各自按当前任务所需拉取子集,然后在本地副本上操作。

由此引申出两点:

  • 一次性把所需子集读取出来,然后在本地操作。 使用在线访问器(.iloc.locdf[col])只读取你需要的行/列;后续的所有处理(过滤、连接、聚合)都在返回的普通 pandas 对象上进行。不要按行或按单元格反复访问缓存。
  • 仅当明显比完整还原更廉价时才提供在线读取。 任何接近 .value 成本的操作(例如 .loc[slice].loc[list].iloc[list])都会被拒绝,并抛出指向 .valueTypeError

变更仅以追加为主。 SeriesObjectDataFrameObject 均支持 extend(...) 进行行追加。DataFrameObject 另外支持通过 __setitem__ / __delitem__ 做结构性的列新增/替换/删除。没有单元格级或原地行变更能力 —— 如需此类操作,请调用 .value 在本地 pandas 对象上操作。


支持范围概览#

支持
行索引 RangeIndex(默认整数索引,紧凑存储)以及任意带标签索引,包括 pd.MultiIndex(按每个 level 存一个 collection:list 子对象)。.index_names 始终返回元组。
DataFrame 列 单层(标量名称) pd.MultiIndex(元组名称)。行轴与列轴相互独立,可组合(DataFrame 可同时拥有行 MultiIndex 与列 MultiIndex)。
值 dtype int64/Int64float64/Float64bool/booleanstr/stringobjectdatetime64(naive 与 tz-aware)、category
变更 extend(Series 与 DataFrame);DataFrame 的列 __setitem__ / __delitem__(结构性的新增/替换/删除)。

说明:

  • .loc 使用 Python == 匹配标签;因此 nan 标签不会匹配(nan != nan)。
  • 返回的部分结果会保留完整的存储索引(不会丢弃任何 level)。

注册#

from gpdatacached.extensions.pandas_support import register
register()   # 幂等

register() 会把 pd.SeriesSeriesObjectpd.DataFrameDataFrameObject 同时注册到 GPDCTypeRegistry。该函数幂等 —— 多次调用无害。在进程启动时、读写任何 pandas 对象之前调用一次即可。


快速开始#

import pandas as pd
from gpdatacached import GPDCDomain, GPDCDomainConfig
from gpdatacached.extensions.pandas_support import register

register()

config = GPDCDomainConfig(redis_host="127.0.0.1", redis_port=6379, redis_password="secret")
domain = GPDCDomain("mydomain", config)
ns = domain.ensure_namespace("analytics")

# ── Series ───────────────────────────────────────────────────────
s = pd.Series([10, 20, 30], name="price", index=["a", "b", "c"])
ns["prices"] = s

series = ns["prices"]                  # → SeriesObject(活跃对象)
print(series.iloc[0])                  # 10(O(1) 位置读取)
print(series.loc["b"])                 # 20(O(N) 选择性标签读取)
print(series.value)                    # 在本地物化的完整 pd.Series

series.extend(pd.Series([40, 50], index=["d", "e"]))   # 原地追加行

# ── DataFrame ────────────────────────────────────────────────────
df = pd.DataFrame({"a": [1, 2], "b": ["x", "y"]})
ns["table"] = df

table = ns["table"]                    # → DataFrameObject(活跃对象)
print(table["a"])                      # 单列作为 pd.Series(O(N),单列)
print(table.iloc[0])                   # 单行作为 pd.Series(O(1))
print(table.loc[[...]])                # ⚡ 被拒绝 —— 请改用 .value

table["c"] = [10, 20]                  # 新增一列(结构性)
del table["b"]                         # 删除一列(结构性)
table.extend(pd.DataFrame({"a": [3], "c": [30]}))   # 原地追加行

print(table.value)                     # 在本地物化的完整 pd.DataFrame

domain.close()

访问器成本指南#

在线访问器存在的唯一理由是明显比 .value 更廉价。请依据下表选择。(GPDC 全库的性能图景 —— sliding TTL、嵌套、GC、加锁 —— 见性能指南。)

访问器 成本 返回
.iloc[int] O(1) 单个位置值/行
.iloc[a:b] O(b−a) 位置切片
.loc[key] O(N),选择性 深度 k 的前缀标签扫描;仅解码匹配的行,但会读取所有索引 level —— 不要放进热循环
df[col] / df[tuple] O(N),单列 (DataFrame)单列,返回 pd.Series
df[level0] / df[list] O(N_cols) + O(匹配数·N) 列轴选择(成本低;列数少)
.value 完整还原 O(N) —— 最终退路

决策规则:

  • 一次性选择性读取(所需行/列远小于总量)→ 使用在线访问器(.iloc / .loc / __getitem__)。
  • 对同一份数据反复或重度操作 —— 排序、大量标签查找、复杂 pandas 语义,或任何被在线 API 拒绝的操作 → 调用一次 .value 在本地物化 pandas 对象后再操作。

SeriesObject#

class SeriesObject(GPDCContainerObject)

GPDC 类型 collection:pandas_series。底层是一个 Redis hash,存储数据列表(匿名 ListObject 子对象)、可选的每个 level 的索引列表,以及标量元信息(dtypenameindex_kindindex_namesindex_dtypesrange_*、category 元信息)。

属性

属性 类型 说明
value pd.Series 物化为普通 pd.Series。setter 永远抛错 —— 请使用变更方法。
type str "collection:pandas_series"
dtype str 存储的值 dtype 字符串(如 "int64""category")。
name Any 存储的 series 名称。
index_names tuple 索引 level 名(始终是元组;扁平索引为单元素)。
iloc _ILocIndexer 位置行访问器(只读)。支持 intslice;拒绝 list。
loc _LocIndexer 基于标签的行访问器(只读)。在带标签/MultiIndex 上支持标量 / 深度 k 的元组前缀。拒绝 slicelist,以及对 RangeIndex 的任何使用,并以指向 .valueTypeError 提示。

方法

方法 说明
extend(other) 原地追加行。接受 pd.Seriespd.Series 列表,或 (index, value) 元组的可迭代对象。修改 Redis;返回 None。(与 pd.concat 不同 —— 后者非修改式。)对 category-dtype series 会抛出 TypeError
deepcopy() 返回完全独立的 pd.Series.value.copy(deep=True))。

协议方法

  • len(series_obj) → 行数。
  • series_obj.iloc[int] → O(1) 位置标量/单元格。
  • series_obj.iloc[a:b] → 位置切片,返回 pd.Series
  • series_obj.loc[key] → 标签选择;全深度(k == L)且仅匹配一行时返回标量值(pandas 语义);否则返回 pd.Series

DataFrameObject#

class DataFrameObject(GPDCContainerObject)

GPDC 类型 collection:pandas_dataframe。底层是一个 Redis hash:列值引用存于 encode_key(col) 之下,每个 level 的行索引标签存于 ~index_l{i} 之下,标量元信息存于 ~ 前缀字段(~columns~columns_names~index_kind~index_names~index_dtypes~range_*,以及每列的 ~dt:{col} / ~cat:{col} / ~ord:{col})。

属性

属性 类型 说明
value pd.DataFrame 物化为普通 pd.DataFrame。setter 永远抛错 —— 请使用变更方法。
type str "collection:pandas_dataframe"
columns tuple 列名(单层为标量,MultiIndex 列为元组)。
index_names tuple 行索引 level 名(始终是元组)。
iloc _DfILocIndexer 位置行访问器(只读)。支持 int(单行作为 pd.Series)和 slice(位置行切片作为 pd.DataFrame);拒绝 list。
loc _DfLocIndexer 基于标签的行访问器(只读)。在带标签/MultiIndex 行索引上支持标量 / 深度 k 的元组前缀。拒绝 slicelist,以及对 RangeIndex 的任何使用,并以指向 .valueTypeError 提示。

方法

方法 说明
extend(other) 原地追加行。接受 pd.DataFrame、list 的 dict,或 dict 行的列表。对于真正的 MultiIndex 行 DataFrame(L ≥ 2),只接受带匹配 MultiIndex 的 DataFrame(dict/list 输入会合成 MultiIndex 无法提供的位置标签 → TypeError;请使用 .value)。修改 Redis;返回 None
deepcopy() 返回完全独立的 pd.DataFrame.value.copy(deep=True))。

列轴访问(单层列)

操作 行为
df[col] 选取单列,返回 pd.Series
df[list_of_keys] 由所选列组成的子 pd.DataFrame(即便只有一个 key 也始终返回子 DataFrame)。
df[col] = values 新增或替换列。len(values) 必须等于当前行数。对空 DataFrame 通过新增列来引入行会被拒绝。
del df[col] 删除列。共享的索引会被保留(即便删到最后一张列)。
col in df 列存在时为 True
iter(df) 遍历列名。

列轴访问(MultiIndex 列)

对于 MultiIndex 列,key 为元组,并应用前缀匹配:

操作 行为
df[(a, b, ...)] 全深度元组 → 该单列作为 pd.Series
df[level0]df[(a, b)] 部分深度前缀 → 由匹配列组成的子 pd.DataFrame
df[list_of_keys] pd.DataFrame(每个 key 可以是标量前缀或全深度元组)。
df[col_tuple] = values 新增或替换全深度列。部分深度元组会抛出 ValueError;非元组或 list key 会抛出 TypeError —— 请调用 .value
del df[col_tuple] 删除全深度列。部分深度元组会抛出 ValueError;非元组或 list key 会抛出 TypeError —— 请调用 .value

协议方法

  • len(df_obj) → 行数。
  • df_obj.iloc[int] → 单行作为 pd.Series(O(1))。
  • df_obj.iloc[a:b] → 位置行切片,返回 pd.DataFrame
  • df_obj.loc[key] → 标签选择;全深度(k == L)且仅匹配一行时返回该行作为 pd.Series;部分深度或匹配多行时返回 pd.DataFrame

异常#

三个异常都派生自 TypeError。它们的错误信息会引导你修改 dtype 或注册自定义 codec。

UnsupportedSeriesDtypeError#

class UnsupportedSeriesDtypeError(TypeError)

当某个 pandas dtype 无法被 SeriesObject 按元素存储时抛出(值 dtype 与索引 dtype 均适用)。在构造时通过 is_index=True/False 在错误信息中区分原因。

错误信息会列出支持的 dtype,并指向 GPDCTypeRegistry.register(...) 作为扩展点。

UnsupportedDataFrameError#

class UnsupportedDataFrameError(TypeError)

当某个 DataFrame 无法被 DataFrameObject 按元素存储时抛出。错误信息会区分原因(每列 dtype、行索引,或重复列名),并指向 GPDCTypeRegistry.register(...)

UnsupportedElementTypeError#

class UnsupportedElementTypeError(TypeError)

当某个 Series/DataFrame 元素无法被 GPDCScalarCodec 子类编码时抛出 —— 即它是容器类型(list/dict/set/容器注册的 pydantic 模型)或不支持的类型。Series/DataFrame 元素必须是能被 GPDCScalarCodec 子类编码的标量类型。错误信息会列举允许的标量类型。