ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

pandas 对照 SQL:从 SELECT、GROUP BY、JOIN 到 UPDATE/DELETE 的完整映射指南

pandas 对照 SQL:从 SELECT、GROUP BY、JOIN 到 UPDATE/DELETE 的完整映射指南 pandas 对照 SQL从 SELECT、GROUP BY、JOIN 到 UPDATE/DELETE 的完整映射指南【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas本篇技术指南以 pandas 官方文档 comparison_with_sql 为核心骨架系统讲解 SQL 中每一类经典操作SELECT、WHERE、GROUP BY、JOIN、UNION、LIMIT、UPDATE、DELETE在 pandas 中的对应实现。读完后你将能够把日常写 SQL 的肌肉记忆直接迁移到 pandas 的 DataFrame/Series API 上理解groupby().size()与count()的区别、merge四种 JOIN 的用法、以及用nlargest/rank复刻 SQL 分析函数ROW_NUMBER()、RANK()的写法。准备工作tips 数据集与 pandas 的“复制语义”文档约定绝大多数示例基于 pandas 测试目录中的tips数据集餐厅小费数据将其读入名为tips的 DataFrame并假设有同名同结构的数据库表。该数据文件在仓库中的实际路径为 pandas/tests/io/data/csv/tips.csv共 244 条记录、7 个字段total_bill,tip,sex,smoker,day,time,size 16.99,1.01,Female,No,Sun,Dinner,2 10.34,1.66,Male,No,Sun,Dinner,3 21.01,3.5,Male,No,Sun,Dinner,3 23.68,3.31,Male,No,Sun,Dinner,2文档给出的读取方式是从 pandas 仓库的 raw 地址加载该 CSVimport pandas as pd import numpy as np url ( https://raw.githubusercontent.com/pandas-dev /pandas/main/pandas/tests/io/data/csv/tips.csv ) tips pd.read_csv(url) tips如果本地已有该文件例如就在本仓库的pandas/tests/io/data/csv/tips.csv把url替换为本地路径即可。在开始逐条对照之前必须先理解 pandas 与 SQL 在“修改数据”这一语义上的根本差异这也是文档专门抽出的一节Copies vs. in place operations对应 includes/copies.rst大多数 pandas 操作返回的是Series/DataFrame的副本。要让修改“生效”要么赋值给新变量sorted_df df.sort_values(col1)要么用返回值覆盖原变量df df.sort_values(col1)部分方法支持inplaceTrue或copyFalse关键字参数例如df.replace(5, inplaceTrue)但 pandas 社区已有提案要对大多数方法如dropna逐步废弃inplace/copy只在极少数方法包括replace中保留在 Copy-on-Write写时复制机制落地后这两个关键字将不再必要。因此实践中更推荐显式赋值返回值的写法。理解这一点后后面所有“过滤、聚合、连接”的示例其结果都应视为新对象而不是对原表的就地更新。SELECT列选择与计算列SQL 中用逗号分隔的列名列表或*来做列选择SELECT total_bill, tip, smoker, time FROM tips;pandas 中向 DataFrame 传入列名列表即可实现同样的列选择tips[[total_bill, tip, smoker, time]]直接调用 DataFrame如tips不加列名列表就等价于 SQL 的SELECT *——显示全部列。SQL 还支持在SELECT中派生计算列SELECT *, tip/total_bill as tip_rate FROM tips;pandas 中对应的是DataFrame.assign方法向原 DataFrame 追加新列而不修改原对象tips.assign(tip_ratetips[tip] / tips[total_bill])注意assign返回的是带有新列的新 DataFrame符合上文所述的复制语义。WHERE布尔索引与条件组合SQL 的WHERE子句对应 pandas 的布尔索引。最直观的过滤方式是把一个布尔Series传给 DataFrame返回所有为True的行文档在 includes/filtering.rst 中展开说明# 等价于 SELECT * FROM tips WHERE total_bill 10 tips[tips[total_bill] 10]也可以先把条件提取成变量便于复用与检查is_dinner tips[time] Dinner is_dinner # 一个 True/False 的 Series is_dinner.value_counts() # 查看条件命中分布 tips[is_dinner] # 等价于 WHERE time Dinner对应 SQLSELECT * FROM tips WHERE time Dinner;AND 与 OR和|与 SQL 的AND/OR类似pandas 使用AND和|OR组合多个条件且每个子条件必须加括号。Dinner 时段且小费大于 5 美元SELECT * FROM tips WHERE time Dinner AND tip 5.00;tips[(tips[time] Dinner) (tips[tip] 5.00)]5 人及以上聚餐或账单总额超过 45 美元SELECT * FROM tips WHERE size 5 OR total_bill 45;tips[(tips[size] 5) | (tips[total_bill] 45)]IS NULL / IS NOT NULLisna()与notna()pandas 用Series.isna()和Series.notna()方法实现空值判断。构造一个示例表frame pd.DataFrame( {col1: [A, B, np.nan, C, D], col2: [F, np.nan, G, H, I]} ) frame假设存在同结构的数据库表frame查询col2为 NULL 的记录SELECT * FROM frame WHERE col2 IS NULL;frame[frame[col2].isna()]查询col1不为 NULL 的记录则使用notna()SELECT * FROM frame WHERE col1 IS NOT NULL;frame[frame[col1].notna()]GROUP BYsize()与count()的区别、多函数聚合SQL 的GROUP BY对应 pandas 同名的DataFrame.groupby方法其过程是把数据集按分组键拆分split、对每组应用函数通常是聚合、再合并combine结果。统计每组的记录数用size()而不是count()最常见的 SQL 操作是统计每组记录条数比如按性别统计小费记录数SELECT sex, count(*) FROM tips GROUP BY sex; -- 结果Female 87Male 157pandas 等价写法tips.groupby(sex).size()这里有一个关键细节用的是DataFrameGroupBy.size()而非DataFrameGroupBy.count()。因为count()是对每一列分别应用返回每组中该列的非空NOT NULL记录数而size()计算的是每组的总行数与 SQL 的count(*)语义一致。从源码看size()的官方文档字符串明确写道“Returns the number of rows in each group”实现入口在 pandas/core/groupby/groupby.py 的size()方法。两者的差异对比tips.groupby(sex).count() # 每列分别计数不含 NaN 的行数 tips.groupby(sex)[total_bill].count() # 只对单列计数一次应用多个聚合函数agg()想看小费金额按星期几如何分布SELECT day, AVG(tip), COUNT(*) FROM tips GROUP BY day; /* Fri 2.734737 19 Sat 2.993103 87 Sun 3.255132 76 Thu 2.771452 62 */pandas 的DataFrameGroupBy.agg支持传入字典指定对各列应用哪些函数tips.groupby(day).agg({tip: mean, day: size})多列分组按多个列分组时向groupby传入列名列表即可SELECT smoker, day, COUNT(*), AVG(tip) FROM tips GROUP BY smoker, day;tips.groupby([smoker, day]).agg({tip: [size, mean]})注意这里agg的字典值可以是一个函数列表[size, mean]对同一列tip同时计算多个统计量结果列会形成多级表头与多列分组键组合后得到 (smoker, day) 两级分组的完整聚合表。JOINmerge的四种连接类型SQL 的JOIN在 pandas 中通过DataFrame.join或pd.merge实现。默认情况下DataFrame.join按索引连接两种方法都提供参数指定连接类型LEFT、RIGHT、INNER、FULL以及连接键列名或索引。警告原文档的 warning 原样继承如果两个键列中都有键值为 null 的行这些行会互相匹配成功——这与常见 SQL JOIN 的行为不同SQL 中 NULL 键通常不会匹配可能导致意外结果需要特别注意。构造两个示例表对应两个同结构的数据库表df1 pd.DataFrame({key: [A, B, C, D], value: np.random.randn(4)}) df2 pd.DataFrame({key: [B, D, D, E], value: np.random.randn(4)})INNER JOINSELECT * FROM df1 INNER JOIN df2 ON df1.key df2.key;# merge 默认执行的就是 INNER JOIN pd.merge(df1, df2, onkey)pd.merge还提供参数支持“一表的列对另一表的索引”进行连接indexed_df2 df2.set_index(key) pd.merge(df1, indexed_df2, left_onkey, right_indexTrue)多列连接SELECT * FROM df1_multi INNER JOIN df2_multi ON df1_multi.key1 df2_multi.key1 AND df1_multi.key2 df2_multi.key2;df1_multi pd.DataFrame({ key1: [A, B, C, D], key2: [1, 2, 3, 4], value: np.random.randn(4) }) df2_multi pd.DataFrame({ key1: [B, D, D, E], key2: [2, 4, 4, 5], value: np.random.randn(4) }) pd.merge(df1_multi, df2_multi, on[key1, key2])如果两侧表的键列名不同用left_on/right_on代替ondf2_multi pd.DataFrame({ key_1: [B, D, D, E], key_2: [2, 4, 4, 5], value: np.random.randn(4) }) pd.merge(df1_multi, df2_multi, left_on[key1, key2], right_on[key_1, key_2])LEFT OUTER JOIN / RIGHT JOIN / FULL JOIN保留df1全部记录LEFTSELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.key df2.key;pd.merge(df1, df2, onkey, howleft)保留df2全部记录RIGHTSELECT * FROM df1 RIGHT OUTER JOIN df2 ON df1.key df2.key;pd.merge(df1, df2, onkey, howright)FULL JOIN 展示两侧全部记录无论是否匹配成功。原文档特别指出截至撰写时并非所有 RDBMS 都支持 FULL JOIN例如 MySQL 就不支持而 pandas 通过howouter直接提供SELECT * FROM df1 FULL OUTER JOIN df2 ON df1.key df2.key;pd.merge(df1, df2, onkey, howouter)UNIONconcat与drop_duplicatesUNION ALL对应pd.concat。构造两个城市排名表df1 pd.DataFrame( {city: [Chicago, San Francisco, New York City], rank: range(1, 4)} ) df2 pd.DataFrame( {city: [Chicago, Boston, Los Angeles], rank: [1, 4, 5]} )SELECT city, rank FROM df1 UNION ALL SELECT city, rank FROM df2; /* city rank Chicago 1 San Francisco 2 New York City 3 Chicago 1 Boston 4 Los Angeles 5 */pd.concat([df1, df2])SQL 的UNION与UNION ALL类似但会去除重复行注意结果中只有一条 Chicago 记录SELECT city, rank FROM df1 UNION SELECT city, rank FROM df2;pandas 中用pd.concat配合DataFrame.drop_duplicates实现pd.concat([df1, df2]).drop_duplicates()LIMIT 与 OFFSEThead()与nlargest()取前 10 行SELECT * FROM tips LIMIT 10;tips.head(10)取“排序后的第 n1 到 nm 行”这类带偏移的 Top-N 查询以 MySQL 语法为例-- MySQL SELECT * FROM tips ORDER BY tip DESC LIMIT 10 OFFSET 5;tips.nlargest(10 5, columnstip).tail(10)这里的nlargest值得展开说明从源码文档字符串pandas/core/frame.py 的DataFrame.nlargest看它“等价于df.sort_values(columns, ascendingFalse).head(n)但性能更好”底层由 pandas/core/methods/selectn.py 中的SelectN系列类实现基于部分选择而非完整排序。参数keep支持first/last/all控制并列值的取舍。因此“排序 跳过 5 行 取 10 行”的组合可以一次nlargest加一次tail完成。分析函数对照每组 Top-N 与 RANK()这是文档中最接近 SQL 窗口函数ROW_NUMBER()、RANK()的部分展示了 pandas 如何“先派生排名列再过滤”来复刻分析函数。每组 Top-N 行对应ROW_NUMBER()SQLOracle 的ROW_NUMBER()分析函数SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER(PARTITION BY day ORDER BY total_bill DESC) AS rn FROM tips t ) WHERE rn 3 ORDER BY day, rn;pandas 等价写法先按total_bill降序排列再用groupby().cumcount() 1生成组内序号rncumcount返回组内 0 开始的行号加 1 即对齐ROW_NUMBER的 1 起始语义最后query过滤( tips.assign( rntips.sort_values([total_bill], ascendingFalse) .groupby([day]) .cumcount() 1 ) .query(rn 3) .sort_values([day, rn]) )也可以用rank(methodfirst)实现同样目标——rank的methodfirst模式会对并列值按出现先后分配不重复名次与ROW_NUMBER()语义一致( tips.assign( rnktips.groupby([day])[total_bill].rank( methodfirst, ascendingFalse ) ) .query(rnk 3) .sort_values([day, rnk]) )RANK() 的对照rank(methodmin)对应 Oracle 的RANK()分析函数SELECT * FROM ( SELECT t.*, RANK() OVER(PARTITION BY sex ORDER BY tip) AS rnk FROM tips t WHERE tip 2 ) WHERE rnk 3 ORDER BY sex, rnk;需求是在小费 2的记录中按性别分组找出排名 3 的行。使用rank(methodmin)时相同tip值的行会得到相同的rnk_min——这正是 OracleRANK()的行为并列名次相同、下一名次跳过( tips[tips[tip] 2] .assign(rnk_mintips.groupby([sex])[tip].rank(methodmin)) .query(rnk_min 3) .sort_values([sex, rnk_min]) )小结这一段的模式pandas 没有独立的窗口子句但assigngroupby().rank()/cumcount()query三步组合可以完整复刻PARTITION BY ... ORDER BY ...的过滤式用法。UPDATE 与 DELETEloc赋值与反向选择SQL 的UPDATE把小费小于 2 的记录翻倍UPDATE tips SET tip tip*2 WHERE tip 2;pandas 用loc的“条件行 目标列”切片直接赋值tips.loc[tips[tip] 2, tip] * 2SQL 的DELETE删除小费大于 9 的记录DELETE FROM tips WHERE tip 9;pandas 的思路是选择要保留的行而不是删除要移除的行——这与全文的“复制语义”一脉相承tips tips.loc[tips[tip] 9]注意这里用tips ...重新绑定变量如果想让其他引用该对象的变量也看到变化需要依赖赋值语义或显式拷贝策略参见前文 Copies 一节的讨论。速查对照表把全文映射关系汇总成一张表方便日常查阅SQL 操作pandas 等价实现SELECT a, b FROM tt[[a, b]]SELECT *, tip/total_bill AS tip_ratetips.assign(tip_ratetips[tip] / tips[total_bill])WHERE cond1 AND cond2df[(cond1) (cond2)]WHERE cond1 OR cond2df[(cond1) \| (cond2)]WHERE x IS NULL/IS NOT NULLdf[df[x].isna()]/df[df[x].notna()]GROUP BY colcount(*)df.groupby(col).size()注意不是count()多列分组 多聚合函数df.groupby([c1, c2]).agg({col: [size, mean]})INNER/LEFT/RIGHT/FULL JOINpd.merge(df1, df2, on..., howinner/left/right/outer)列名不一致的连接pd.merge(..., left_on[...], right_on[...])列对索引连接pd.merge(df1, indexed_df2, left_onkey, right_indexTrue)UNION ALLpd.concat([df1, df2])UNION去重pd.concat([df1, df2]).drop_duplicates()LIMIT ndf.head(n)ORDER BY x DESC LIMIT n OFFSET mdf.nlargest(n m, columnsx).tail(n)ROW_NUMBER() OVER (PARTITION BY ...)sort_values(...).groupby(...).cumcount() 1或groupby(...).rank(methodfirst)RANK() OVER (PARTITION BY ...)groupby(...)[col].rank(methodmin)UPDATE ... SET ... WHERE ...df.loc[条件, col] 新值DELETE ... WHERE ...df df.loc[保留条件]两点需要额外提醒其一merge中两侧键列的 NULL 值会互相匹配这与多数 SQL 数据库的行为不同其二pandas 操作大多返回副本UPDATE/DELETE式修改必须用loc就地赋值或重新绑定变量sort_values这类操作则必须写回变量才能生效。完整示例可回看 comparison_with_sql 及其引用的 includes/copies.rst、includes/filtering.rst测试数据源在 pandas/tests/io/data/csv/tips.csvnlargest的底层实现可参考 pandas/core/methods/selectn.py。【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表