尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

科普:由几个函数形成的“复合函数”:Series.map(dict(zip(A,B)))

科普:由几个函数形成的“复合函数”:Series.map(dict(zip(A,B))) 一、 PandasSeries.map(dict(zip(A,B)))列映射在 Pandas 开发中经常会遇到这样的处理目的手上有两组数据一组存放编码和对应属性可视为字典另一张表Series只有编码希望根据编码把对应的属性批量补充到第二张表里。Series.map(dict(zip(A,B)))就是专门解决该需求的一行式写法。整条表达式可以拆成两个核心部分字典可能不是现成的有时需要组装常用的组装方法dict(zip(A,B))把A列当作键、B列当作值构建{编码:属性}的查询字典Series.map(字典)拿着建好的字典对目标Series中每一个编码做查表返回映射完成的新列。全文统一使用下面这份模拟数据importpandasaspd# items编码‑属性数据源保存商品编号与是否易腐的对应关系itemspd.DataFrame({item_nbr:[101,102,103],perishable:[1,0,1]})# df_2017待处理数据表只有商品编码缺少易腐标记需要补充该字段df_2017pd.DataFrame({item_nbr:[101,101,102,103,103,101]})完整一行调用直接完成属性映射回填df_2017[perishable_flag]df_2017[item_nbr].map(dict(zip(items[item_nbr],items[perishable].values)))下面我们对它进行拆解并扩展知识点。二、dict(zip(A,B))由两列构造查询字典上述的zip(A, B)接收两个可迭代对象严格按位置顺序配对生成一组(key, value)元组迭代器。dict()接收这一组元组转换为Python字典。# 将商品编号、易腐标记按位置配对zzip(items[item_nbr],items[perishable].values)print(list(z))# [(101, 1), (102, 0), (103, 1)]# 生成可用于查表的字典perish_dictdict(zip(items[item_nbr],items[perishable].values))print(perish_dict)# {101: 1, 102: 0, 103: 1}使用 zip 的关键约束配对只看行位置不会按值做匹配查找。源表行一旦乱序生成的字典映射关系直接出错必须保证同一行内A、B是正确的编码‑属性。如果两个输入序列长度不一致zip会以更短的序列为准截断丢弃长序列多余部分造成映射丢失。list(zip([1,2],[10,20,30]))# [(1, 10), (2, 20)]zip返回迭代器只能遍历一次如需多次复用需要提前转为list。Pandas原生替代方案set_index to_dict除了dict(zip(A,B))Pandas 提供更贴合DataFrame的方式生成完全相同的映射字典。set_index()将普通列提升为行索引再对取值列调用.to_dict()直接输出映射字典。perish_dictitems.set_index(item_nbr)[perishable].to_dict()这里需要理清set_index()与reset_index()的行为这两个是构造映射时高频用到的辅助方法df.set_index(item_nbr)普通列item_nbr移动到行索引该字段不再作为普通列存在除了配合.to_dict()也支持.loc按标签查询。df.reset_index()把当前行索引降级还原为普通列同时生成0‑N的数字行索引。区分两种场景DataFrame本身就是默认数字索引调用reset_index()只会新增一列存放旧行号原有业务列不受影响此时属于多余操作。如果已经执行过set_index(item_nbr)item_nbr已经变成索引无法直接以列名取值这时才需要reset_index()把索引恢复成普通列。操作作用列变化适用场景df.set_index(item_nbr)普通列提升为行索引原普通列移除变为index配合.to_dict()生成映射.loc标签查询df.reset_index()索引降级为普通列索引转为普通列生成0‑N数字索引把自定义索引恢复普通列原始数字索引调用无业务意义构造映射字典的两条等效路径路径一dict(zip(key列, value列))适合快速临时构造路径二df.set_index(key列)[value列].to_dict()正式项目优先选用三、Series.map()使用字典完成整列查表拿到映射字典之后将字典传入Series.map()即可对整列做批量查表生成新的Series。perish_dictitems.set_index(item_nbr)[perishable].to_dict()# 根据商品编码映射得到易腐标记df_2017[perishable_flag]df_2017[item_nbr].map(perish_dict)对比手写Python for循环实现相同逻辑不推荐大数据同样的业务需求可以用原生循环实现但性能、容错行为和map有明显区别。perish_dictdict(zip(items[item_nbr],items[perishable].values))flag_list[]foritemindf_2017[item_nbr]:flag_list.append(perish_dict[item])df_2017[perishable_flag]flag_list性能循环运行在Python虚拟机百万级样本会明显变慢。容错原生字典直接取值编码不存在时直接抛出KeyError程序中断。Series.map(字典)底层由Cython实现哈希查表循环在C层执行速度比Python手写循环高出几十到上百倍内部自带key存在判断找不到编码会填充np.nan不会抛出异常。实践经验大数据量表内编码属性回填避免手写Python for循环优先使用Series.map(映射字典)。map的两套执行分支Series.map(arg)会判断入参类型执行两套完全独立逻辑。分支1传入字典上面使用的路径Cython实现高速哈希查找缺失key返回np.nan不会报错。下面是Python伪代码模拟内部逻辑importnumpyasnpdefseries_map_dict(s,mapping_dict):result[]forelemins:ifeleminmapping_dict:result.append(mapping_dict[elem])else:result.append(np.nan)returnpd.Series(result,indexs.index)分支2传入函数 / lambda每一个元素都会调用一次传入的Python函数产生Python层调用开销大表映射不推荐。defseries_map_func(s,func):result[]forelemins:valfunc(elem)result.append(val)returnpd.Series(result,indexs.index)示例功能可以实现但不推荐# ❌不推荐走函数分支丢失内置key容错未知编码直接抛出KeyErrordf_2017[p2]df_2017[item_nbr].map(lambdax:perish_dict[x])伪源码示意内部分支判断defmap(self,arg,na_actionNone):ifisinstance(arg,dict):returnself._map_dict(arg)elifcallable(arg):returnself._map_function(arg,na_actionna_action)else:raiseTypeError(arg must be a dict or callable)na_action 参数该参数只对传入函数的场景生效控制空值处理逻辑na_actionNone默认NaN同样送入函数执行na_actionignore遇到NaN直接返回NaN不调用传入函数。spd.Series([1,np.nan,3])s.map(lambdax:x*10,na_actionignore)# 输出10.0 , NaN , 30.0Series.map 的能力边界返回全新Series不会修改原始数据索引和输入保持一致只能访问单个元素无法访问同一行其他列。需要同时操作多列时改用apply(axis1)。思考题分析下面两段代码行为差异#写法1df_2017[p1]df_2017[item_nbr].map(perish_dict)#写法2df_2017[p2]df_2017[item_nbr].map(lambdax:perish_dict[x])答案写法1传入字典进入字典分支内置key校验未知编码输出NaN写法2传入lambda进入函数分支lambda执行原生字典取值缺失key直接抛出KeyError。
返回列表