资讯动态

Pandas数据筛选与过滤:布尔索引、loc/iloc与缺失值处理

发布时间:2026/10/27 22:07:12 来源:尧图企业网站定制
做数据分析这行日常被问得最多的一句话不是模型怎么调而是帮我把这批数据里符合某某条件的行挑出来。Pandas 数据筛选和过滤说白了就是把人手在 Excel 里点筛选、勾条件、拉公式的那套动作翻译成可复用、可批处理、可进版本库的代码。这一章我打算掰开讲四件事布尔索引的地基逻辑、loc和iloc的边界、query()这类表达式写法的取舍、以及文本和缺失值这两块最脏的数据怎么过滤。适合已经能读写 CSV、会建 DataFrame但一遇到多条件组合、链式赋值警告、NaN参与比较就心里没底的同学也适合带新人、需要一套靠谱口径的老手对照。标题里的(1)不是凑数这一篇只覆盖行级条件筛选的主干按位置、按类型、按名称这类工具型 API 一并收进来下一部分再谈分组后筛选、合并后筛选和索引层面的过滤。1. 筛选这件事先把它放回真实数据流里看1.1 筛选在一条数据流水线上的真实位置很多人学 Pandas 的顺序是先学read_csv再学groupby最后回头补筛选结果就是写业务代码时到处是先聚合再判断的别扭逻辑。我实际的习惯恰好反过来一条数据流水线里筛选出现的频率远高于聚合。读进来先砍掉无关行清洗阶段挑出异常行单独看建模前切训练集和对照集出报表前按维度过滤全是筛选。这里有个容易忽略的点筛选本身不产生新信息它只是缩小后续计算的输入规模。所以判断一次筛选写得好不好标准不在于代码短不短而在于它有没有尽早把数据量降下来以及它有没有误杀。前者影响性能后者影响结论。我见过太多因为NaN被静默丢掉、导致转化率算高了的案例问题都不在聚合那一步出在筛选那一步。从工程角度看筛选还承担一个隐性职责它是最便宜的数据质量探针。你按某个条件去筛返回 0 行那大概率不是业务真的没有而是字段类型不对、空格没清、编码不一致。所以我养成一个习惯任何一个新数据集进来先用几个最基础的筛选条件试一下行数变化比看info()更能发现问题。1.2 先想清楚的问题你要的是标签还是位置这是我认为整章最该先讲明白的一件事比任何语法都重要。Pandas 的索引体系里同时存在两套坐标系一套是标签也就是index上挂的那个值可能是整数、可能是字符串、可能是时间戳另一套是位置从 0 开始的第 N 个。这两套东西在默认的RangeIndex下长得一模一样于是新手很容易产生它们是一回事的错觉直到某天做了sort_values或者drop之后索引被打乱代码就开始返回莫名其妙的结果。我的判断口径很粗暴只要是条件判断一律用布尔索引或loc只有明确要前 N 行第 3 到第 8 行这种纯序数语义才用iloc。理由很简单条件判断表达的是业务含义业务含义应该跟标签走而序数语义表达的是物理位置物理位置在数据一变之后就失效了。把这两者混着用代码在测试环境能跑换一批数据就翻车。顺带说一句索引乱掉之后最省事的做法不是reset_index()了事而是先想清楚这个索引还有没有价值。如果它原本是从业务系统带过来的唯一键那它就是你后面做关联、做校验的抓手别随手扔掉如果它只是个临时序号那reset_index(dropTrue)让一切回到干净状态反而是更稳的选择。1.3 这一篇的内容边界标题叫(1)我把范围划成这样行级条件筛选的主干方法包括布尔索引、loc/iloc、query()、文本方法、缺失值判断、按数据类型和名称筛。这些覆盖了日常 80% 以上的场景。至于分组之后的组内筛选groupbyfilter、多表合并之后的筛选、以及基于多层索引的切片属于另一个话题放到后面单独说因为它们各自有独立的坑硬塞进一篇会讲不透。有一点提前说明本文所有代码基于 Pandas 2.x 的接口1.x 大部分也适用个别地方我会标出版本差异比如between()的inclusive参数。数据全部是模拟的订单表不涉及任何真实业务。2. 布尔索引所有筛选动作的地基2.1 一行布尔表达式背后到底发生了什么df[df[amount] 100]这行代码很多人当成一个原子操作实际上它是三步。第一步df[amount] 100取出这一列底层是一个 NumPy 数组做逐元素的向量化比较产出一个长度相同的bool数组第二步把这个bool数组当作掩码传给df.__getitem__第三步Pandas 按掩码为True的位置把行取出来组装成一个新的 DataFrame。理解这三步的价值在于第一步的产物是一个独立的 Series你可以先存下来复用。多条件场景下这一点特别有用。我经常这么写mask_valid (df[status] paid) df[amount].notna() mask_big mask_valid (df[amount] 500) print(mask_valid.sum(), mask_big.sum()) df_big df[mask_big]先把掩码存成变量一是能打印sum()看看命中了多少行二是同一个掩码在后续好几处筛选里都能复用省掉重复计算。对于百万行以上的表一个布尔掩码的构建成本在几十毫秒量级复用的收益是实打实的。还有一层这个布尔数组每个元素占 1 字节。1000 万行的表一个掩码就是约 10MB 内存三个条件同时存在于内存里就是 30MB。听起来不多但在内存吃紧的环境里把int64的金额列先转成float32、把重复度高的状态列转成category能省下的往往是几百 MB这时候一个 10MB 的中间掩码就不算事了。优化的优先级永远是大头先动。2.2 多条件组合|~和括号的血泪教训这一段是新手翻车率最高的地方我把三个必踩的坑摆出来。第一个坑用and/or代替/|。Python 的and依赖bool()求值而一个长度大于 1 的 Series 是无法转成单一布尔值的直接抛ValueError: The truth value of a Series is ambiguous。必须用位运算符。这三个符号在 Pandas 里的语义是逐元素逻辑与、或、非跟 Python 的短路逻辑完全是两回事。第二个坑运算符优先级。的优先级高于和所以df[a] 1 df[b] 2会被解析成df[a] (1 df[b]) 2报错或者给出完全错误的结果。每个条件都用括号包起来不是风格问题是正确性问题。我自己的写法习惯是连最外层也包一层虽然冗余但复制粘贴到别的表达式里不会出事。第三个坑空值在比较里的静默行为。这一条最隐蔽单独开一小节说。三个以上条件时可读性会急剧下降。这时候我会做一件事给每个条件起个名字。cond_status df[status].isin([paid, shipped]) cond_amount df[amount].between(100, 2000) cond_channel df[channel] ! internal_test cond_time df[created_at] 2024-01-01 result df[cond_status cond_amount cond_channel cond_time]条件一多出问题时你能立刻定位是哪个条件写错了而不是对着一行两百字符的表达式数括号。2.3between()和isin()把区间和集合写干净连续区间和离散集合这两类条件用比较运算符硬写也能写但有更省事的写法而且能顺带避开空值问题的一部分。between(left, right)等价于 left且 right但从 Pandas 1.3 开始多了inclusive参数可以取both默认、neither、left、right。做分箱统计、划分金额档位时这个参数很顺手。注意它对NaN的处理和普通比较一致NaN参与between结果仍是False而不是NaN。isin(values)判断是否属于某个集合底层用的是哈希查找比逐个再|起来快得多特别是集合里有十几个值的时候。它对NaN的处理有个细节值得记一下import numpy as np import pandas as pd s pd.Series([a, b, np.nan]) print(s.isin([a])) # [True, False, False] print(s.isin([a, np.nan])) # [True, False, True]也就是说isin是能把NaN匹配出来的只要你显式把np.nan放进候选集合里。这跟的行为完全不同np.nan np.nan是False知道这一点在做排除某几个值时能救命。还有一个组合技巧取反用~包isin比写一串!加清晰得多。比如排除测试渠道df_clean df[~df[channel].isin([internal_test, sandbox])]但要注意~作用于isin的结果时NaN的isin结果是False取反就变成True于是NaN行会被保留。这通常是你想要的但你得知道它发生了。2.4 空值在布尔条件里的诡异表现NaN的比较行为可以用一句话总结除了!所有比较都返回False。NaN 100是FalseNaN 100是FalseNaN 100也是False。这听起来很合理但它带来一个非常反直觉的后果df[df[amount] ! 100]不会包含amount为NaN的行。为什么因为NaN ! 100在浮点语义里是True但 Pandas 在做掩码时如果比较结果里出现NaN对象类型或可空类型时会把它当非布尔处理更常见的情况是数值列里的NaN参与比较直接得到False于是!的反向筛选就漏掉了空值行。绕开的办法有两个。一是显式补上df[(df[amount] ! 100) | df[amount].isna()]。二是先决定空值算哪边再用fillna统一处理。我自己的习惯是先统计空值占比占比很低就单独拿出来看占比高就说明这个字段本身有问题得回头找上游。还有一类坑出在可空类型Int64、boolean、string上。Pandas 引入的这套扩展类型保留了pd.NA比较结果可能是pd.NA而不是True/False拿去做布尔索引会报cannot mask with non-boolean array containing NA / nan values。这不是 bug是在提醒你这个条件的语义没定义清楚。解决办法是fillna(False)或者用.astype(bool)之前先确认没有 NA。3.loc与iloc把标签和位置彻底分开3.1loc的切片是闭区间这点坑过太多人df.loc[a:c]会把a、b、c三行都取出来而df.iloc[0:2]只取第 0、1 两行。loc闭区间iloc左闭右开。这条规则记不住做时间序列切片时会少一天或者多一天而且不会报错只会让统计结果差一点是最难查的那种问题。我自己的记法是loc是我要从 A 到 B业务语义上通常包含两端比如1 月 1 日到 1 月 31 日当然包含 31 日iloc是我要第 0 个开始数 2 个这是编程语义当然是左闭右开。按语义记比死记硬背靠谱。时间索引上还有个额外的坑如果你用字符串2024-01去切DatetimeIndexPandas 会做部分字符串匹配把整个 1 月都切出来。这个特性很方便但要求索引必须是有序的否则可能返回空或者报KeyError。做时间切片之前sort_index()一下是我多年养成的肌肉记忆。3.2iloc的纯位置逻辑与两种越界iloc只认位置不认标签所以它最大的优势是行为可预测不管索引被sort_values打乱成什么样iloc[0]永远是物理第一行。取首行、取末行、取前 N 行这类操作我基本都用它或者head()/tail()。越界有两种形态。硬越界是iloc[100]但表只有 50 行直接IndexError这个好办。软越界是iloc[45:100]不会报错只是给你 5 行悄无声息。做分页或者分块处理时这种少给了也没提示的行为要特别注意通常得自己校验返回长度。还有一个实用但少人用的点iloc支持负数iloc[-1]是最后一行。但要注意iloc[-0]等价于iloc[0]不是倒数第一个。3.3 行列同时筛选以及链式索引那个老问题loc的完整签名是df.loc[行选择器, 列选择器]两个维度可以同时给条件。这在只需要几列的场景下很有价值sub df.loc[(df[amount] 500) df[status].eq(paid), [order_id, amount, created_at]]一次性拿到需要的列比先筛行再切列少一次中间对象的构造。数据量大的时候这个差别不算小。然后是那个绕不开的SettingWithCopyWarning。它的成因是df[df[amount] 100][flag] 1这种链式写法第一步返回的可能是一个视图也可能是副本Pandas 无法确定于是第二步赋值可能改在原表上也可能改在一个即将被丢弃的临时对象上。解决办法不是加copy()压警告而是改写成单次locdf.loc[df[amount] 100, flag] 1一行搞定语义明确改的一定是df本身。这个写法我建议直接刻进肌肉记忆凡是按条件改某列的值一律用df.loc[条件, 列] 值。3.4at/iat做标量访问df.at[行标签, 列标签]和df.iat[行位置, 列位置]是单值访问比loc/iloc快因为省掉了数组包装的开销。日常写代码用得不多但在循环里取值、或者写测试断言时很顺手。要注意at一次只能取一个值传切片会报错这一点和loc不同。4.query()把条件写成一句话4.1 语法糖背后的性能甜点query()让你用字符串写筛选条件列名不用反复写df[xxx]df.query(amount 500 and status paid)注意这里可以用and/or因为整个表达式是被解析成类 Python 语法再编译的不是在 Python 层面做布尔求值。这也是它相比布尔索引的一个便利点不用记着换成和|。性能上如果环境里装了numexprquery()会把表达式编译成字节码并对大型数组做分块并行计算在千万行量级、条件包含多个算术运算时优势比较明显。没装numexpr的话它会退化成普通的求值路径性能跟布尔索引差不多甚至略慢一点因为多了字符串解析的开销。4.2 引用外部变量、带空格的列名、字符串匹配三个高频用法。引用外部变量用lower, upper 100, 2000 df.query(amount lower and amount upper)列名带空格或者跟 Python 关键字冲突时用反引号包起来df.query(order status paid)。字符串匹配直接在表达式里写df.query(channel.str.startswith(online), enginepython)这里必须加enginepython因为默认的numexpr引擎不支持.str方法。这一点不写清楚很容易让人以为query不支持字符串操作。4.3 什么时候别用query()query()不是万能的。首先它不支持所有表达式比如某些复杂的自定义函数调用、多层索引的某些访问方式。其次条件里的变量如果名字跟列名撞了忘了写就会静默取列而不是取变量这种 bug 极难查。第三条件动态拼接时容易出现字符串注入式的错误比如值里带引号。我的口径是条件简单、一次性的探索分析用query()写进生产代码的筛选用布尔索引或loc。原因是生产代码需要静态可检查字符串里的错误编辑器发现不了跑起来才报。探索阶段图快query()确实省打字。5. 文本与缺失值真实数据里最脏的两块5.1str方法的正确姿势与 NA 处理文本筛选离不开.str访问器。df[name].str.contains(张)、.str.startswith(...)、.str.len() 3、.str.upper() ABC这几个覆盖了绝大部分需求。最关键的一条.str.contains()的结果里源数据是NaN的位置返回NaN不是False。拿这个结果直接做布尔索引会报cannot mask with non-boolean array containing NA / nan values。正确写法是显式指定mask df[remark].str.contains(退款, naFalse)naFalse的意思是空值当作不匹配绝大多数业务场景下这是对的。如果你反过来想把空值也算作命中那就naTrue。这个小参数漏写是新手在文本筛选上最常见的报错来源。另外.str方法对非字符串列会返回NaN比如一列里混了数字和字符串dtype是objectstr.contains只对字符串元素生效数字元素得到NaN。稳妥的做法是先astype(string)统一成可空字符串类型再操作。5.2 正则筛选的写法与性能权衡str.contains默认就把参数当正则处理所以df[code].str.contains(r^A\d{4}$)能直接筛出A 加四位数字的编码。regexFalse可以关掉正则纯字面量匹配会快一些而且不用担心(、.、*这些字符被当成元字符。性能上正则匹配是逐元素跑的 C 层实现比 Python 循环快得多但仍然是全表扫描。几百万行的文本列做复杂正则耗时可能到秒级。优化的思路是先用便宜的等值或前缀条件缩范围pre df[df[code].str.startswith(A, naFalse)] hit pre[pre[code].str.contains(r^A\d{4}$, regexTrue, naFalse)]startswith走的是前缀比较比全正则便宜先砍掉大部分行再做精细匹配实测能省一半以上时间。还有str.match和str.contains的区别match只匹配开头等价于正则加^fullmatch要求整串完全匹配。这三个选错结果会莫名其妙地多筛了或少筛了。5.3isna/notna把缺失值当成一个维度来筛isna()和notna()返回布尔 Series可以直接用于索引也可以配合any(axis1)找出任意一列为空的行rows_with_any_na df[df.isna().any(axis1)] rows_all_na df[df.isna().all(axis1)]df.isna().sum()是每次拿到新表必跑的一行按列统计缺失数量比df.info()更直观因为它给出了具体数字你能立刻判断哪些列需要处理。有一点容易被忽略空字符串和NaN是两回事isna()抓不到空字符串。上游系统导出的数据里和 空格非常常见。所以我的清洗习惯是两步走先replace把空白字符统一成NaN再走isna逻辑df[name] df[name].replace(r^\s*$, np.nan, regexTrue) df[name] df[name].str.strip()顺序很重要先替换再 strip或者先 strip 再替换都行但不能只做一步因为 经过strip变成还需要再被识别为空。6. 工具型 API按类型、按名称、按大小筛6.1select_dtypes按数据类型批量挑列做特征工程时经常要把所有数值列拿出来统一处理或者把对象列单独看。select_dtypes就是干这个的num_cols df.select_dtypes(includenumber).columns.tolist() obj_cols df.select_dtypes(include[object, string]).columns.tolist()include和exclude可以同时用。有个细节要留意bool在 NumPy 里算number的子类所以includenumber会把布尔列也带出来。不想要的话加excludebool。这个坑我踩过一次把一个标记列当成数值列做了标准化结果全是 0 和 1 被压成负数排查了半天。6.2filter()筛的是列名不是内容这是本章最需要强调的认知纠正df.filter(likeamount)筛的是列名或索引名不是按行内容筛。它的三个参数items精确名称列表、like名称包含、regex名称正则全都是在名字上做文章。想要按内容筛回到布尔索引。它的典型用途是从一个上百列宽表里挑出某一组同前缀的列比如所有dim_开头的维度列dim_df df.filter(regex^dim_)这个场景下它比手写列表推导清晰得多而且不会因为记忆漏了某列。6.3nlargest/nsmallest/sample/where/mask几个高频小工具放一起说。nlargest(n, col)和nsmallest(n, col)是排序后取前 N的优化实现内部用的是堆比sort_values().head(n)快。有keep参数控制并列时的保留策略first/last/all默认first做 TopN 排行榜时会用到all把并列的都留下。sample(n100, random_state42)是随机抽样做数据探查时比head()靠谱得多因为head()只看开头而排序过的数据开头往往不代表全体。固定random_state保证可复现写进报告里别人能对着同一批样本看。where(cond, other)和mask(cond, other)是条件替换前者不满足条件的替换成 other后者满足条件的替换成 other两者互为反面。它们不改原表返回新表。清洗时把异常值打成NaN很顺手df[amount_clean] df[amount].where(df[amount].between(0, 100000))配合inplace参数也能改原表但我不推荐用inplace可读性差而且链式调用时不安全。方法筛选维度典型场景是否常用布尔索引df[...]行内容任意条件组合极高loc行标签 列标签复杂条件 选列极高iloc行列位置首尾行、前 N 行高query行内容探索分析、条件较长中filter列名 / 索引名按前缀挑列中select_dtypes列的 dtype批量特征处理中nlargest某列数值TopN中where/mask行内容条件替换中7. 完整实操一份模拟订单表的筛选流程7.1 先把数据造出来光讲语法没意思走一遍真实流程。下面这份模拟订单表故意埋了几个常见的脏数据形态状态有大小写混用、金额有空值、渠道里有测试入口、备注里有空字符串和NaN并存。import numpy as np import pandas as pd rng np.random.default_rng(7) n 20000 df pd.DataFrame({ order_id: [fOD{i:06d} for i in range(n)], status: rng.choice([paid, Paid, PAID, pending, refunded], n), channel: rng.choice([online_web, online_app, offline, internal_test], n, p[0.4, 0.35, 0.2, 0.05]), amount: rng.gamma(2.0, 300, n).round(2), created_at: pd.date_range(2024-01-01, periodsn, freqmin), remark: rng.choice([, , 正常, 退款申请, np.nan], n, p[0.1, 0.05, 0.6, 0.15, 0.1]), }) # 人为制造缺失和异常 df.loc[rng.choice(n, 300, replaceFalse), amount] np.nan df.loc[rng.choice(n, 50, replaceFalse), amount] -999跑完先做一遍体检这一步别省print(df[status].value_counts()) print(df.isna().sum()) print(df[remark].replace(r^\s*$, np.nan, regexTrue).isna().sum())status那一列你会看到paid、Paid、PAID三个值如果不去重直接按 paid筛会丢掉三分之二的已支付订单。这是我在真实项目里遇到次数最多的一类问题而且它不报错只是结果偏小。7.2 分步筛出目标人群目标是2024 年上半年、非测试渠道、状态为已支付忽略大小写、金额在 0 到 50000 之间、备注里提到退款的订单。第一步状态统一df[status_clean] df[status].str.lower().str.strip()第二步逐个条件写清楚方便单独验证c_status df[status_clean].eq(paid) c_channel ~df[channel].eq(internal_test) c_amount df[amount].between(0, 50000) # NaN 自动为 False c_time df[created_at].between(2024-01-01, 2024-06-30 23:59:59) c_remark df[remark].str.contains(退款, naFalse) mask c_status c_channel c_amount c_time c_remark print({k: int(v.sum()) for k, v in { status: c_status, channel: c_channel, amount: c_amount, time: c_time, remark: c_remark}.items()}) print(最终命中:, int(mask.sum()))这一步的价值全在中间那行打印。每个条件的命中数单独看一眼如果c_remark只有 3 行而你的预期是几百行那说明备注字段的清洗有问题而不是数据真的没有。我见过太多人一口气把五个条件括起来最后结果不对只能一个个注释掉重跑。第三步拿到结果并只保留需要的列result df.loc[mask, [order_id, status_clean, amount, created_at, remark]] result result.sort_values(amount, ascendingFalse)第四步顺手验证一下有没有漏网。把status的原始值和清洗值做个交叉对照print(pd.crosstab(df[status], df[status_clean]))如果出现某个原始值清洗后落到意料之外的类别说明.str.lower()之外还有别的脏东西比如前后空格或者全角字符。7.3 几种写法的耗时对比条件一样写法不同速度会有差别。下面这组数据是在一台普通开发笔记本上跑的20 万行数据重复 20 次取中位数只看相对量级别当绝对值写法相对耗时说明布尔索引 1.0基准最直接无额外解析开销query()无 numexpr约 1.2多了表达式解析query()有 numexpr约 0.7多条件算术运算时优势明显.isin()代替多组|约 0.4集合越大优势越明显nlargest代替sort_values().head()约 0.5n 远小于总行数时几条结论条件里的离散值超过 3 个优先换isin多条件涉及算术运算比如amount * rate 100且数据量大装个numexpr会有惊喜TopN 场景别用排序。还有一点容易被忽略筛选要尽早做。如果你后面还要merge两个大表先把无关行筛掉再合并内存峰值会低一大截。合并之后再筛是最费内存的顺序因为中间结果全量存在。8. 常见问题速查与踩坑记录8.1 一张速查表现象大概率原因处理方式ValueError: truth value of a Series is ambiguous用了and/or换成/|每个条件加括号cannot mask with non-boolean array containing NA条件结果含NaNstr.contains(..., naFalse)或fillna(False)筛选结果少了空值行!把NaN排除了补| df[col].isna()结果比预期少很多字符串大小写、前后空格不一致先str.lower().str.strip()再比切片少了一行iloc左闭右开改用loc或把右端点 1SettingWithCopyWarning链式赋值改成df.loc[cond, col] value筛选 0 行但数据明明有列是字符串型而条件写的是数字确认 dtype必要时astype布尔索引返回 DataFrame 而不是按行筛列名重复取出的是 DataFrame检查df.columns.duplicated()8.2 我在实际用的时候总结的几条经验第一永远先看value_counts()和缺失统计。这不是流程里的可选项是筛条件之前的必做动作。你对数据分布的假设八成会在这一步被推翻。特别是那种应该只有几个枚举值的状态列实际往往有十几个还带着大小写变体。第二把掩码当中间变量存下来。一是能打印命中数做验证二是能在后续多处复用三是出了问题时可以单独拿这个掩码去df[mask].head()看实际命中的行长什么样。很多人调试筛选问题就是反复改条件重跑其实把掩码拿出来看一眼样本行问题立刻现形。第三改值一律用df.loc[cond, col] value。不接受其他写法。链式索引这个坑只要踩过一次导致数据被改错就会变成肌肉记忆。第四条件多的时候先做减法再做加法。我的习惯是先按最严格、最便宜的条件筛一遍把数据量降到一个能head(20)看完的规模再在子集上逐步加条件验证。反过来先加所有条件、最后发现结果不对排查成本高一个量级。第五别迷信一行流。网上很多一行代码实现 XX 筛选的写法可读性极差条件一改就崩。生产代码里我更愿意写四五行中间变量每个变量名说清一个业务含义。代码是给人看的机器只负责跑。第六筛选前后各记一次行数。print(len(df), len(result))这行看着傻但它能在第一时间告诉你掉了几成数据。如果掉了 90% 而你预期只掉 10%问题就在这一次筛选里不用往下查。8.3 几个容易混淆的边界情况df[cond]和df.loc[cond]在大多数情况下等价但列名重复时前者会出问题后者更稳。所以我现在基本统一写loc。df[[a, b]]是选列df[cond]是筛行同一个__getitem__根据传入类型走不同分支。传列表是选列传布尔 Series 是筛行传切片是筛行且和iloc的切片语义在某些版本上有微妙差异。知道这个分支逻辑遇到怪问题时能猜到方向。dropna()看起来是筛选但它默认howany会把任意一列有空值的行全删掉。一个 30 列的表只要有一列缺失率高dropna()能把表删空。用之前一定加subset限定列或者干脆别用改用dropna(subset[关键列])。最后提一个我自己的偏好筛选逻辑尽量写成纯函数输入 DataFrame 和参数输出筛选后的 DataFrame不做inplace修改。这样每一步都能单独测出了问题能二分定位比一坨写在一起的脚本强太多。数据量再大也不差这一次拷贝的内存。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑