尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

数据预处理实战指南:SQL、Python与R的协同清洗方案

数据预处理实战指南:SQL、Python与R的协同清洗方案 简介数据预处理是数据分析与建模的基石直接影响后续结果的可信度。面对重复、缺失、格式混乱的原始数据如何高效完成清洗与整合SQL、Python和R三套工具各有优势SQL擅长在海量数据库内进行去重、过滤和聚合资源占用低Python的pandas库灵活处理多源异构数据适合复杂逻辑和特征工程R的dplyr、tidyr生态则在统计探索和快速验证中表现优异。理解三者原理并组合使用能大幅提升数据清洗效率支撑报表开发、特征工程和业务分析等场景。本文从通用数据预处理概念出发结合实践案例展示如何用SQL处理大表、用Python清洗杂表、用R辅助验证形成一套可复用的协同工作流。 做数据分析这些年我越发觉得真正决定一个项目成败的往往不是那个花哨的机器学习模型而是前端那一步毫不起眼的数据预处理。数据预处理说白了就是“从原始数据到可用数据”的过程。你从数据库导出来的表、爬虫抓下来的页面、业务部门发来的Excel基本都是脏的、乱的、缺的、类型不对的。如果这一步不做扎实后面建模、可视化、报表全都会跟着崩。这篇文章想认真聊一聊我在项目中常用的三套工具SQL、R和Python。它们不是竞争关系而是不同场景下的不同选择。全文不会只给结论我会把每一步背后的为什么讲清楚附上可以直接复制修改的源码并把我在实操中踩过的坑、总结的排查经验一并放出来。适合正在做数据清洗、特征工程、报表开发的人参考也适合刚入门想系统理解“数据预处理到底在干嘛”的读者。1. 内容整体设计与思路拆解1.1 数据预处理到底解决什么问题如果把数据分析比作做饭数据预处理就是“洗菜切菜”。菜不洗泥沙吃进嘴里菜不切锅都放不下。尤其在企业环境里数据来源通常是多个系统业务库、日志表、第三方接口、手工维护的Excel……这些数据有不同的格式、不同的编码、不同的命名习惯甚至同一个“客户ID”在不同表里可能是字符型和数值型。数据预处理的核心任务我归纳成四类一是“清”把重复、错误、无关的数据去掉二是“补”处理缺失值把空缺填上或标记三是“转”统一类型、统一格式、统一单位四是“并”把多张表、多个文件整合成一张可分析的表。无论是SQL、R还是Python做的都是这四件事只是侧重点和适用场景不同。1.2 为什么选择SQL、R、Python这三样很多初学者喜欢问“到底学哪个好”我的回答是先想清楚你的工作场景。SQL是结构化查询语言它最大的优势在于数据还在数据库里时就能完成清洗不占用本地内存处理千万级甚至亿级数据都不慌。企业里最重的数据分析任务第一步几乎都是SQL完成的。而且SQL的语法相对简单去重、过滤、关联、聚合等操作一套标准语法吃遍几乎所有数据库。如果你的团队用Oracle、PostgreSQL、MySQL、SQL Server这套技能是通用资产。R的优势在统计建模和探索性分析。它天生适合做“一个人拿到数据后想快速做各种验证”的场景。dplyr、tidyr、stringr这一套tidyverse体系把数据操作包装得非常优雅。配合RStudio的交互界面写几行代码就能看到结果非常适合做数据探索和可视化的前置清洗。Python则是“全能型选手”。pandas在数据清洗上的表现非常突出尤其适合数据不在数据库里而是来自多个文件、接口或者要做复杂自定义逻辑的场景。再加上Python能无缝衔接机器学习、深度学习、自动化脚本R在某些场景下还要靠Python配合才能完成全流程。所以我在这篇文章里的设计思路不打算讲“谁替代谁”而是给出一个组合拳的思路数据量大的、常规的推给SQL需要探索、验证的用R快速试需要做复杂逻辑、或者数据来自非数据库源头的交给Python。这个思路本身就是我多年实践下来最好用的。2. SQL篇数据库里的清洗艺术2.1 去重不止是DISTINCT这么简单很多人提到SQL去重第一反应是SELECT DISTINCT但实际业务里这个命令远远不够。DISTINCT是“整行去重”意思是所有列都相同才去重但真实数据往往会出现“主键相同其他字段不同”的情况这时候我们需要定义“按哪个字段去重、保留哪一条”。我最常用的方式是利用窗口函数ROW_NUMBER()。比如有一张订单表同一个订单号可能出现了多条记录字段值各不一样我们想按订单号去重保留最新的一条WITH ordered AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY order_id ORDER BY update_time DESC ) AS rn FROM orders ) SELECT * FROM ordered WHERE rn 1;这段代码的逻辑是先按order_id分组在组内按update_time倒序排序给每一行编一个序号最后只取序号为1的行。为什么不用GROUP BY因为GROUP BY配合MAX(update_time)虽然可以拿到最新时间但要同时取这一整行的其他字段还是得回头关联原表反而麻烦。窗口函数一步到位而且可读性高后续如果改成“保留最早的一条”或“按多个字段排序取优先”只需要调整ORDER BY即可。注意不是所有数据库都支持窗口函数。MySQL 8.0以上、PostgreSQL、SQL Server 2012以上、Oracle都支持如果是老版本的MySQL 5.7就得用关联子查询来替代性能上也会差一些。2.2 缺失值与空值的处理SQL里的空值有NULL和空字符串之分这两者在大多数数据库里含义并不一样。NULL代表“未知”空字符串代表“存在但是空白”。我在做预处理时通常会把这两种情况统一处理。常见的操作是在WHERE条件里过滤或者用COALESCE赋值默认值SELECT customer_id, COALESCE(phone, 无电话) AS phone_filled, COALESCE(NULLIF(email, ), 无邮箱) AS email_filled FROM customers;这段代码里有两个关键函数NULLIF(email, )是把空字符串转换成NULL然后COALESCE再把NULL变成“无邮箱”。这样处理的好处是后续不管是做统计还是做展示都不会因为空值的类型不同而产生偏差。还有一点容易被忽略COUNT(column)和COUNT(*)的结果不一样。COUNT(*)统计所有行COUNT(column)只统计该列非NULL的行数。在检查数据质量时我经常利用这一点快速判断缺失程度SELECT COUNT(*) AS total_rows, COUNT(email) AS email_not_null, COUNT(phone) AS phone_not_null FROM customers;如果total_rows是10000email_not_null只有8000说明这个字段缺失率大概20%后续就要评估是补齐还是丢弃。2.3 数据类型转换与标准化从业务系统导出的数据经常会出现“明明是数值但存成了字符型”的情况。比如“订单金额”列里有逗号千分位或者前面带了个不要的货币符号。在做统计或排序前必须先把类型转对。SQL Server里的CAST和CONVERT是常规做法MySQL里也可以用CAST。如果字符串里混入了符号就得先清理再转换-- SQL Server SELECT order_id, CAST(REPLACE(REPLACE(amount_str, ,, ), ¥, ) AS DECIMAL(10, 2)) AS amount FROM raw_orders; -- MySQL SELECT CAST(REPLACE(REPLACE(amount_str, ,, ), ¥, ) AS DECIMAL(10, 2)) AS amount FROM raw_orders;标准化也是预处理里很重要的一环。比如“性别”字段有的表存的是0/1有的是F/M有的是“男/女”在做跨表合并时一定要统一。我习惯在SQL里直接用CASE WHEN把原始值映射成统一标准SELECT user_id, CASE WHEN gender IN (男, M, 1) THEN M WHEN gender IN (女, F, 0) THEN F ELSE UNKNOWN END AS gender_new FROM users;这种写法在数据质量抽查时特别好用一眼就能看出哪些值没有进入映射规则进而发现脏数据。2.4 慢查询优化对预处理效率的影响数据预处理往往是整个流程里最耗时的一步如果SQL写得很烂几亿行的表跑一个多小时后面的工作全被卡住。我自己的经验是在做大数据量的清洗时优先考虑三件事第一过滤条件要早。能用WHERE把数据量降下来的不要在SELECT里处理完再过滤。第二关联时优先选小表驱动大表并且确保关联键上有索引。第三避免在WHERE条件里对字段做函数操作比如WHERE DATE(created_at) 2024-01-01这会导致索引失效正确做法是WHERE created_at 2024-01-01 AND created_at 2024-01-02。SQL的性能问题不只是DBA的事做数据预处理的人也必须有这个敏感度。写一条能正确跑出结果但效率极低的SQL跟写一条又好又快的SQL是两种完全不同的能力。3. R篇统计老司机的数据整理工具箱3.1 dplyr的数据操作管道用过R的人基本都绕不开dplyr。它厉害的地方在于把数据操作抽象成了几个动词filter筛选、select选列、mutate新增列、summarise汇总、arrange排序。配合管道符%%读起来就像在说人话。我处理数据时最常见的场景是读入一份CSV然后做筛选、去重、生成新列。下面这个例子演示了完整的清洗流程library(dplyr) library(readr) # 读取原始数据设置col_types避免类型猜错 df - read_csv( sales_raw.csv, col_types cols( order_id col_character(), amount col_double(), item_count col_integer() ) ) # 去重按order_id保留最后一条 df_clean - df %% arrange(order_id, desc(update_time)) %% distinct(order_id, .keep_all TRUE) %% filter(!is.na(amount) amount 0) %% mutate( amount_winsor if_else(amount quantile(amount, 0.99), quantile(amount, 0.99), amount), order_month format(update_time, %Y-%m) )这段代码里有几个细节值得说破distinct(order_id, .keep_all TRUE)的意思是按order_id去重同时保留第一次出现的行配合arrange先排序就可以控制“保留哪一条”。这跟SQL里的ROW_NUMBER()是一个思路。if_else做极端值截断winsorize也非常实用。真实业务里的金额数据常有特别大的异常值如果直接进入模型会把均值拉得很高。按99%分位数截断一下既保留了数据的分布形态又抑制了极端值的影响。3.2 tidyr让长表和宽表随意切换长表和宽表的转换是数据预处理里绕不开的场景。报表展示通常要宽表一行一个样本画图、建模时经常要长表一行是一个观测。tidyr的pivot_longer和pivot_wider就是为了解决这个问题。比如有这样的宽表一列是一个月12列就是12个月现在画趋势图需要把它变成“月份”和“销售额”两列library(tidyr) sales_long - sales_wide %% pivot_longer( cols starts_with(month_), names_to month, values_to sales ) %% mutate(month str_remove(month, month_))反过来如果想做“每个月各产品的销售额”这种汇总展示可以用pivot_wider把长表转宽sales_summary - sales_long %% group_by(product, month) %% summarise(sales sum(sales, na.rm TRUE), .groups drop) %% pivot_wider( names_from month, values_from sales, values_fill 0 )values_fill 0很关键它把缺失的组合填成0避免透视结果里出现大片的NA。在业务报表里NA经常会被误读为“没数据”或“错误”用一个显式的数值占位可以减少沟通成本。3.3 stringr处理字符串的坑R的字符串处理历史上有段“野史”基础函数grep、substr用起来不够顺手stringr把接口统一成了str_开头可读性和一致性提升很多。在清洗地址、手机号、邮箱等半结构化字段时我基本全套stringr。举一个实战例子清洗“联系电话”字段把里面的非数字字符全部去掉再判断位数是否合法library(stringr) df_clean - df %% mutate( phone_digits str_replace_all(phone, [^0-9], ), phone_valid if_else(str_length(phone_digits) 11, Y, N) )这里用到的str_replace_all(phone, [^0-9], )是一个正则表达式[^0-9]表示“所有不是0-9的字符”把它们替换成空串。如果原始电话里有138-1234-5678处理后会变成13812345678再判断长度是否为11位。提示在抓取或导出的数据里电话字段经常混有余空格、全角空格、短横线、括号这种“非数字字符清零”的清洗思路比逐个替换符号要快得多。3.4 进阶用purrr批量处理多文件数据处理经常会遇到几十个甚至上百个格式相同的CSV文件要合并。用Excel手动复制粘贴会让人崩溃R里可以用purrr包优雅解决library(purrr) library(readr) library(dplyr) # 获取目录下所有csv文件路径 file_list - list.files(data/raw/, pattern \\.csv$, full.names TRUE) # 批量读取并合并到一张表 df_all - file_list %% map(~ read_csv(.x, show_col_types FALSE)) %% bind_rows()如果每个文件都带一个“来源日期”字段而文件名里恰好有日期信息还可以这样补充df_all - file_list %% map_dfr(function(path) { read_csv(path, show_col_types FALSE) %% mutate(source_file basename(path)) })这里的核心思路是把“对单个文件的操作”打包成一个函数再用map批量应用到所有文件上。这个模式我用了无数次比自己写for循环再rbind要清晰得多而且出错的概率更低。4. Python篇pandas实战4.1 DataFrame的读取与基础检查Python处理数据预处理的核心是pandas。拿到数据后的第一件事我从来不是急着清洗而是先做“体检”看看形状、列名、类型、缺失情况、重复情况。import pandas as pd df pd.read_csv(sales_raw.csv, dtype{order_id: str}) # 基础体检 print(df.shape) print(df.info()) print(df.isna().sum()) print(df.duplicated().sum())df.info()会打印每一列的非空数量、数据类型、内存占用。这个输出对判断“哪些列需要转类型、哪些列缺失严重”非常直观。比如某列是object类型但实际内容是数字那就需要pd.to_numeric转换某列有大量NaN就需要决定填充还是删除。还有一个我特别喜欢的命令是df.describe()它会输出数值列的分位数、均值、标准差用极短时间就能发现异常值。比如看到max比75%高出好几个数量级基本可以确定存在极端值后面清洗时要重点处理。4.2 清洗组合拳去重、替换、类型转换pandas的数据清洗最常用的几个操作是drop_duplicates、replace、astype、pd.to_datetime。它们单独拎出来都很简单但组合起来才能形成真正的清洗流程。下面是一个涵盖多种情况的示例# 去重保留最后一条 df df.drop_duplicates(subset[order_id], keeplast) # 把 男/M/1 都统一成 M df[gender] df[gender].astype(str).str.strip() gender_map {男: M, M: M, 1: M, 女: F, F: F, 0: F} df[gender] df[gender].map(gender_map).fillna(UNKNOWN) # 金额去掉逗号和货币符号转成float df[amount] ( df[amount_str] .astype(str) .str.replace(,, , regexFalse) .str.replace(¥, , regexFalse) .astype(float) ) # 日期多种格式统一成datetime df[order_date] pd.to_datetime(df[order_date], errorscoerce)这里drop_duplicates(subset[order_id], keeplast)里的keeplast是保留最后一条跟keepfirst正好相反具体用哪个取决于业务规则。pd.to_datetime里的errorscoerce的意思是遇到无法解析的日期就置为NaT缺失时间而不是报错中断。这在脏数据较多的场景下特别有用能保证批量处理继续进行。4.3 缺失值处理的三种思路缺失值处理是数据预处理中最有“艺术感”的部分。我的原则是能不删行就不删行能不用全局均值就不用全局均值尽量用更合理的信息来做填充。第一种是固定值填充。比如“性别”缺失用“UNKNOWN”填充比如“渠道来源”缺失用“未知渠道”填充。这种方式适用于分类变量。第二种是前后填充。时间序列数据里某个传感器在某个时间点没采集到数据用ffill前向填充或bfill后向填充往往比均值更合理因为它保持了趋势的连续性。df[value] df[value].ffill()第三种是分组填充。这是被低估的一种方式比如每个城市的用户收入可能有明显差异如果把它整体用均值填充城市间的差异就会被抹平但按城市分组再取组内均值填充效果会好很多。df[income] df.groupby(city)[income].transform(lambda x: x.fillna(x.mean()))transform的作用是把“按城市分组后的均值”广播回每一行这样长度与原始DataFrame一致可以直接拿去填充。注意填充缺失值前一定要确认导致缺失的原因。如果缺失是系统性造成的比如上游系统某个月没接入那么填充一个历史均值就是在制造误导信息。这时候做缺失标记比填充更有价值。4.4 特征工程常用操作特征工程可以看作是数据预处理的高阶阶段目标是把原始字段加工成更适合模型输入的特征。pandas里最常见的操作是分组聚合、分桶、文本向量化。分桶就是把连续值离散化。比如“年龄”字段可以分成“未成年、青年、中年、老年”几档df[age_group] pd.cut( df[age], bins[0, 18, 35, 60, 120], labels[未成年, 青年, 中年, 老年] )分组聚合有时候是把多行合并成一行生成“用户级”特征。比如每个用户的订单数、总金额、最近一次购买距今天数user_features ( df.groupby(user_id) .agg( order_count(order_id, count), total_amount(amount, sum), last_order_date(order_date, max) ) .reset_index() ) user_features[recency_days] ( pd.Timestamp(2024-12-31) - user_features[last_order_date] ).dt.days这种“用户维度特征表”在客户分层、复购预测类项目里几乎是标配也是数据预处理中“并”的典型应用把订单表、用户表、商品表合并成一张宽表。5. 三剑客如何协同一个完整的实战案例5.1 需求拆解我拿一个实际做过的项目来说。背景是这样的业务方提供了几个数据源一个是MySQL里的订单表一个是本地CSV里的用户注册日志还有一堆Excel里存的商品目录。需求是生成一份“用户订单汇总表”包含用户基本信息、订单数量、订单总金额、最近下单时间供后续RFM分析使用。这个需求如果只用一种工具也可以做但会很别扭。比如订单表千万级在Excel里打不开用户注册日志里有大量半结构化字段SQL处理起来麻烦商品目录又涉及到Excel多Sheet读取。所以我的方案是第一步用SQL做主表的清洗和聚合第二步用Python读取CSV和Excel做用户信息和商品目录的清洗第三步在Python里把三部分合并输出最终宽表。5.2 各环节选择SQL、R还是Python确定分工的逻辑很简单数据量大、且在数据库里优先SQL数据不在库里、涉及自定义清洗逻辑优先Python如果中间想快速做几个探索性图表我会先用R做个初步统计验证清洗规则是不是合理。在这套流程里SQL负责跑重活订单表去重、过滤异常订单、按用户聚合。Python负责接杂活读Excel、清用户注册日志、合并数据。R的角色更像是“质检员”在合并完成后用R快速画几个分布图检查数据是否符合预期。比如画一下订单金额的分布如果发现有个尖峰就回头查是哪个环节引入的脏数据。5.3 代码实现与结果对比第一步在MySQL里做订单表的清洗和聚合WITH valid_orders AS ( SELECT user_id, order_id, amount, order_date, ROW_NUMBER() OVER ( PARTITION BY order_id ORDER BY order_date DESC ) AS rn FROM orders WHERE status ! cancelled ) SELECT user_id, COUNT(order_id) AS order_count, SUM(amount) AS total_amount, MAX(order_date) AS last_order_date FROM valid_orders WHERE rn 1 GROUP BY user_id;第二步用Python读取CSV并清洗用户注册日志import pandas as pd users pd.read_csv(users_regist.log, sep\t) users[user_id] users[user_id].astype(str) users[phone] ( users[phone] .astype(str) .str.replace(r\D, , regexTrue) ) users[gender] users[gender].map({M: M, F: F}).fillna(UNKNOWN)第三步读取Excel商品目录假设有多个Sheet只取需要的商品有效状态goods_sheet pd.read_excel(goods_catalog.xlsx, sheet_namevalid_goods) goods_sheet[product_id] goods_sheet[product_id].astype(str)最后在Python里把订单聚合结果从SQL导出为CSV、用户信息、商品表合并order_user pd.merge(order_agg, users, onuser_id, howleft) final_df pd.merge(order_user, goods_sheet, onproduct_id, howleft) final_df.to_csv(final_user_order_summary.csv, indexFalse)这个案例看起来不复杂但它的价值在于每张表都在自己最合适的环境中做预处理。如果硬拿着千万级订单表去R里跑内存会爆炸要是非要让SQL去解析Excel也不是不行但成本高得多。分工协作才是数据预处理的正确姿势。6. 常见问题与排查技巧实录6.1 编码问题编码问题是我遇到频率最高的障碍。导出CSV时用Excel打开乱码、读取不同系统的接口数据时出现UnicodeDecodeError都是老生常谈。排查思路就一句话明确编码来源。如果是公司内部的系统导出的先看数据库连接串里的characterEncoding如果是手动保存的CSV问一下是Windows还是Mac环境导出的。Windows下的Excel默认保存CSV通常是gbk或gb2312Mac和Linux下通常是UTF-8。Python读取时的打开方式# 尝试UTF-8失败后尝试GBK try: df pd.read_csv(data.csv, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(data.csv, encodinggbk)R读取时df - read.csv(data.csv, fileEncoding UTF-8) # 如果失败就换成 fileEncoding GBK还有一个我自己常用的排查技巧用文本编辑器打开CSV看前几行特殊字符是不是“”这种替换字符一看到这个基本可以判断不是UTF-8直接换GBK再试。6.2 内存爆炸R和Python处理大数据时都有内存爆炸的坑。R里加载一个几GB的数据框直接卡死Python里pd.read_csv读取大文件内存飙升。处理思路是分块读取。Python的pandas支持chunksize参数chunk_list [] for chunk in pd.read_csv(big_data.csv, chunksize100000): chunk_clean chunk[chunk[amount] 0] chunk_list.append(chunk_clean) df pd.concat(chunk_list, ignore_indexTrue)这样每次只读取10万行清洗完再拼起来内存占用会平稳很多。R里则推荐用data.table包的fread它的加载速度比read.csv快非常多而且内存控制更好library(data.table) dt - fread(big_data.csv)SQL那边就更简单了直接在数据库里做聚合只把结果查出来。所以遇到大文件我的第一反应是先想能不能在SQL里解决而不是硬扛到本地。6.3 日期时间格式混乱日期时间格式的混乱几乎是所有真实项目的共同痛点。同一个字段里有的行是2024/01/01有的是2024-01-01有的是01/01/2024还有的带时间戳2024-01-01 12:30:00。我的经验是先统一成字符串再强制转换转换失败的行单独抽出来检查。Python里的处理df[date_str] df[date_str].astype(str).str.strip() df[date_dt] pd.to_datetime(df[date_str], formatmixed, errorscoerce)formatmixed是pandas 2.0以后提供的能力可以自动匹配多种日期格式但如果数据里既有01/02/2024又有02/01/2024仍然有歧义。真正的功夫在转换前要先通过抽样看数据形态再决定统一的解析规则。6.4 三套工具连接数据库时的注意点SQL、R、Python都可以连接数据库做预处理但连接时有一些细节需要注意。SQL不用说直接在数据库客户端里跑R和Python连数据库时最好把查询结果先存成CSV或parquet再做后续清洗。这样做的原因是直接远程读数据库表如果表很大传输本身就是瓶颈还会占用数据库连接资源。Python连MySQL的常用方式import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passhost:3306/dbname?charsetutf8mb4) df pd.read_sql_query(SELECT * FROM orders WHERE order_date 2024-01-01, engine)R连接数据库时我一般用DBI加odbclibrary(DBI) library(odbc) con - dbConnect(odbc(), dsn mysql_dsn, database dbname) df - dbGetQuery(con, SELECT * FROM orders LIMIT 100000)无论哪种方式一个核心建议是不要用代码直接拉全表而是把时间范围、关键过滤条件放到SQL里让数据库先帮你把数据量降下来。这才是三套工具协同的正确使用姿势。7. 一些值得多说几句的实操思路写到这里数据预处理的SQL、R、Python主干内容已经讲完。但在实际操作层面我还想补充几个容易被忽略但影响很大的思路。第一个是“先做数据字典再动手”。拿到一个新数据源不要急着写清洗代码先花几十分钟把每一列的含义、类型、取值范围、缺失情况列出来做成一份简易数据字典。这个动作看似浪费时间但能帮你提前发现很多坑比如某列是日期型但里面有0000-00-00、某列是数值型但存在-9999这种特殊标记。提前知道这些清洗的时候就不会被突如其来的脏数据打断节奏。第二个是“在清洗前先把原始数据备份一份”。我见过太多同事直接在原表上UPDATE操作完发现逻辑写错了结果原始数据也没了只能重新导出。正确做法是先用SQL建一张备份表或者把原始CSV复制一份再在副本上处理。这个习惯能在关键时候救你一命。第三个是“每个清洗步骤都要能追溯”。处理完数据后如果有人问“这个字段为什么都是这么填的”你能不能回答上来如果处理脚本是Python尽量用函数封装每一步打上日志如果是SQL尽量用CTEWITH子句分层写每一层只做一件事。这样既容易排查问题也方便后期维护。我自己的习惯是在SQL里每一层CTE都加上注释说明这个层做了什么处理、为什么这么做在Python里则是把函数名定义得像一句话比如clean_phone_to_digits、fill_missing_gender。代码能跑只是个开始能被别人看懂、能回滚、能复现代才是真正有价值的数据预处理。如果你现在正被一堆“看似能跑但不知道是否可信”的数据折磨不妨把SQL、R、Python按这个思路结合起来试一试大表交给SQL杂表交给Python探索验证交给R。这个组合我已经用了很多年希望也能帮你少走点弯路。本文还有配套的精品资源点击获取
返回列表