第4章:分支与循环——让代码学会“思考”和“重复”

各位好,我是老张。今天咱们聊聊数据清洗里最核心的两个控制结构:分支和循环。

说白了,分支就是让代码做判断,循环就是让代码重复干活。这两个东西,我几乎每天都会用到。你想想看,清洗数据时是不是经常遇到这种情况:某个字段为空要特殊处理,某个值超出范围要标记异常,或者要对每一行数据做同样的转换操作?嗯,这些场景,都离不开分支和循环。

4.1 if判断语句:最简单的决策

先说说if。它的逻辑特别直白:如果条件成立,就执行某段代码。

我在项目中遇到过这样一个场景:清洗用户年龄数据时,有些年龄字段是负数,明显是录入错误。这时候if就派上用场了。

age = -5
if age < 0:
    print("年龄异常,需要处理")

这里要注意,if后面的条件表达式,结果必须是布尔值(True或False)。Python里,0、空字符串、空列表、None这些都会被当作False,其他都是True。嗯,这个特性有时候会坑人,我后面会讲。

4.2 运算符:判断的“工具”

判断离不开运算符。常用的有这么几类:

类型 运算符 说明
比较运算符 ==, !=, >, <, >=, <= 比较两个值
逻辑运算符 and, or, not 组合多个条件
成员运算符 in, not in 判断是否在序列中
身份运算符 is, is not 判断是否是同一个对象

我个人习惯,在数据清洗时最常用的是比较运算符和逻辑运算符。比如检查一个值是否在合理范围内:

score = 85
if score >= 0 and score <= 100:
    print("分数有效")
else:
    print("分数异常")
小技巧:Python支持链式比较,上面的条件可以写成 if 0 <= score <= 100:,更简洁。

4.3 if-else语句:二选一

if-else就是“如果...否则...”的逻辑。数据清洗里最常见的场景:判断数据是否有效,有效则保留,无效则标记或丢弃。

我曾经处理过一个电商订单数据,里面有个“支付金额”字段,有些记录是负数(可能是退款操作)。我的处理逻辑是这样的:

amount = -50.0
if amount >= 0:
    print(f"正常订单,金额:{amount}")
else:
    print(f"异常订单,金额为负:{amount},需人工复核")

这里有个坑:如果你用if-elif-else处理多个分支,一定要把最严格的条件放在最前面。为什么?因为Python是按顺序判断的,一旦某个条件满足,后面的就不会再执行了。

避坑指南:我曾经在写评分等级判断时,把条件顺序搞反了。先判断了“大于60分及格”,结果所有60分以上的都被归为“及格”,后面的“优秀”、“良好”分支永远执行不到。记住:范围判断要从窄到宽。

4.4 while循环:不知道重复多少次时用

while循环适合那种“不知道要循环多少次,但知道什么时候停止”的场景。比如从文件里逐行读取数据,直到文件末尾。

我记得有一次清洗一个超大CSV文件,不能用pandas一次性读入内存,就用while逐行处理:

with open('huge_data.csv', 'r') as f:
    line = f.readline()
    while line:
        # 处理每一行数据
        process_line(line)
        line = f.readline()

这里要注意:while循环一定要有终止条件,否则就是死循环。我刚开始学的时候,就写过死循环,程序卡死,只能强制关闭。嗯,那种感觉,挺尴尬的。

4.5 for循环:知道要循环多少次时用

for循环更适合“遍历一个序列”的场景。比如遍历列表、字典、DataFrame的行等。数据清洗中,for循环的使用频率远高于while。

举个例子,清洗用户数据时,需要把每个用户的手机号做脱敏处理:

phone_numbers = ['13812345678', '15987654321', '17766668888']
for phone in phone_numbers:
    masked = phone[:3] + '****' + phone[7:]
    print(masked)

输出结果:

138****5678
159****4321
177****8888

我个人习惯,能用for循环解决的问题,绝不用while。因为for更安全,不会出现忘记写终止条件的问题。

4.6 break和continue:循环的“刹车”和“跳过”

这两个关键字,是循环里的控制利器。

  • break:立即终止整个循环,不再执行后续的迭代。
  • continue:跳过当前这次迭代,直接进入下一次。

我在项目中遇到过这样一个需求:从一堆数据里找第一个满足条件的记录,找到就停止。这时候break就很好用:

data = [12, 45, 78, 23, 56, 89, 34]
for value in data:
    if value > 80:
        print(f"找到第一个大于80的值:{value}")
        break

而continue常用于“过滤”场景。比如清洗数据时,跳过那些无效的记录:

records = [{'name': '张三', 'age': 25}, 
           {'name': '李四', 'age': None}, 
           {'name': '王五', 'age': 30}]

for record in records:
    if record['age'] is None:
        continue  # 跳过年龄为空的数据
    print(f"{record['name']}的年龄是{record['age']}")
重点提醒:break和continue只作用于它们所在的当前循环。如果你有嵌套循环,它们只影响最内层的那一个。这个细节,我当年调试了半小时才搞明白。

4.7 实战组合:分支+循环清洗数据

最后,咱们来个综合案例。假设你有一个用户评分数据,需要做以下清洗:

  1. 评分必须在1-5之间,超出范围的标记为“异常”
  2. 评分为空的,用默认值3填充
  3. 统计有效评分的数量
raw_scores = [5, 3, None, 8, 4, 2, None, 1, 6, 3]
cleaned_scores = []
valid_count = 0

for score in raw_scores:
    if score is None:
        # 空值用默认值3
        cleaned_scores.append(3)
        valid_count += 1
        continue
    
    if score < 1 or score > 5:
        # 超出范围,标记为异常
        cleaned_scores.append('异常')
        continue
    
    # 正常评分,直接保留
    cleaned_scores.append(score)
    valid_count += 1

print(f"清洗后数据:{cleaned_scores}")
print(f"有效评分数量:{valid_count}")

输出结果:

清洗后数据:[5, 3, 3, '异常', 4, 2, 3, 1, '异常', 3]
有效评分数量:8

你看,分支和循环配合使用,数据清洗的逻辑就变得清晰了。我个人习惯,写这种逻辑时,先把所有可能的异常情况列出来,然后用if-elif-else逐个处理,最后再处理正常情况。这样代码不容易漏掉边界条件。

好了,这一章的内容就到这里。分支和循环是编程的基础,也是数据清洗的基石。你想想看,没有这些控制结构,我们只能处理单条数据,有了它们,就能批量处理成千上万条记录了。下一章,咱们聊聊函数和模块,让代码更整洁、更可复用。