第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("分数异常")
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是按顺序判断的,一旦某个条件满足,后面的就不会再执行了。
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']}")
4.7 实战组合:分支+循环清洗数据
最后,咱们来个综合案例。假设你有一个用户评分数据,需要做以下清洗:
- 评分必须在1-5之间,超出范围的标记为“异常”
- 评分为空的,用默认值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逐个处理,最后再处理正常情况。这样代码不容易漏掉边界条件。
好了,这一章的内容就到这里。分支和循环是编程的基础,也是数据清洗的基石。你想想看,没有这些控制结构,我们只能处理单条数据,有了它们,就能批量处理成千上万条记录了。下一章,咱们聊聊函数和模块,让代码更整洁、更可复用。