|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在数据分析的旅程中,数据清洗是最基础也是最关键的一步。俗话说”垃圾进,垃圾出”,无论你的分析模型多么复杂,如果输入的数据质量不高,结果也必然不可靠。据估计,数据科学家通常花费60%-80%的时间在数据清洗和准备工作上。掌握高效的数据清洗技能,不仅能提高你的工作效率,还能确保后续分析的准确性和可靠性。
pandas作为Python生态中最强大的数据处理库,提供了丰富而灵活的工具来应对各种数据清洗挑战。本教程将从零开始,带你逐步掌握pandas数据清洗的核心技能,帮助你轻松应对数据分析中的各种清洗问题。
pandas基础
在开始数据清洗之前,我们需要先了解pandas的基本概念和数据结构。
安装和导入pandas
如果你还没有安装pandas,可以通过以下命令安装:
安装完成后,我们可以在Python脚本中导入pandas:
- import pandas as pd
- import numpy as np # 通常与pandas一起使用
复制代码
pandas的核心数据结构
pandas有两个核心数据结构:Series和DataFrame。
Series:一维标记数组,能够保存任何数据类型(整数、字符串、浮点数、Python对象等)。
- # 创建一个Series
- s = pd.Series([1, 3, 5, np.nan, 6, 8])
- print(s)
复制代码
输出:
- 0 1.0
- 1 3.0
- 2 5.0
- 3 NaN
- 4 6.0
- 5 8.0
- dtype: float64
复制代码
DataFrame:二维标记数据结构,可以看作是一个表格或Series对象的集合。
- # 创建一个DataFrame
- data = {'Name': ['Tom', 'Nick', 'John', 'Tom'],
- 'Age': [20, 21, 19, 20],
- 'Score': [85.5, 90.0, 76.5, 85.5]}
- df = pd.DataFrame(data)
- print(df)
复制代码
输出:
- Name Age Score
- 0 Tom 20 85.5
- 1 Nick 21 90.0
- 2 John 19 76.5
- 3 Tom 20 85.5
复制代码
数据加载与初步检查
数据清洗的第一步是加载数据并进行初步检查,了解数据的基本情况。
加载数据
pandas支持多种数据格式的加载,包括CSV、Excel、SQL数据库等。
- # 从CSV文件加载数据
- df = pd.read_csv('data.csv')
- # 从Excel文件加载数据
- df = pd.read_excel('data.xlsx')
- # 从SQL数据库加载数据
- import sqlite3
- conn = sqlite3.connect('database.db')
- df = pd.read_sql('SELECT * FROM table_name', conn)
复制代码
查看数据基本信息
加载数据后,我们需要查看数据的基本信息,包括数据维度、前几行、数据类型等。
- # 查看数据维度(行数和列数)
- print(df.shape)
- # 查看前5行数据
- print(df.head())
- # 查看后5行数据
- print(df.tail())
- # 查看数据的基本信息
- print(df.info())
- # 查看数据的统计摘要
- print(df.describe())
- # 查看各列的数据类型
- print(df.dtypes)
复制代码
示例:加载数据集并初步检查
让我们使用pandas内置的泰坦尼克号数据集作为示例:
- # 加载泰坦尼克号数据集
- df = pd.read_csv('https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv')
- # 查看数据维度
- print(f"数据维度: {df.shape}")
- # 查看前5行数据
- print("\n前5行数据:")
- print(df.head())
- # 查看数据基本信息
- print("\n数据基本信息:")
- df.info()
- # 查看数值型列的统计摘要
- print("\n数值型列的统计摘要:")
- print(df.describe())
复制代码
通过这些初步检查,我们可以了解到数据的基本情况,包括数据量、列名、数据类型、缺失值情况等,为后续的数据清洗工作奠定基础。
处理缺失值
缺失值是数据清洗中最常见的问题之一。pandas中缺失值通常表示为NaN(Not a Number)。
检测缺失值
- # 检查每列的缺失值数量
- print(df.isnull().sum())
- # 检查整个数据框的缺失值数量
- print(df.isnull().sum().sum())
- # 检查哪些行包含缺失值
- print(df[df.isnull().any(axis=1)])
- # 检查每列的缺失值比例
- missing_percentage = df.isnull().mean() * 100
- print(missing_percentage)
复制代码
处理缺失值的方法
处理缺失值有几种常见策略:删除、填充和插值。
- # 删除包含缺失值的行
- df_dropna_rows = df.dropna()
- # 删除包含缺失值的列
- df_dropna_cols = df.dropna(axis=1)
- # 删除所有值都为缺失值的行
- df_dropna_all = df.dropna(how='all')
- # 删除至少有n个非缺失值的行
- df_dropna_thresh = df.dropna(thresh=5) # 至少有5个非缺失值的行才会被保留
复制代码- # 用特定值填充缺失值
- df_filled = df.fillna(0)
- # 用列的平均值填充缺失值(仅适用于数值型列)
- df_mean_filled = df.fillna(df.mean())
- # 用列的中位数填充缺失值(仅适用于数值型列)
- df_median_filled = df.fillna(df.median())
- # 用众数填充缺失值(适用于分类数据)
- df_mode_filled = df.fillna(df.mode().iloc[0])
- # 用前一个值填充缺失值(向前填充)
- df_ffill = df.fillna(method='ffill')
- # 用后一个值填充缺失值(向后填充)
- df_bfill = df.fillna(method='bfill')
- # 对不同的列使用不同的填充策略
- df_specific_fill = df.fillna({
- 'Age': df['Age'].median(),
- 'Cabin': 'Unknown',
- 'Embarked': df['Embarked'].mode()[0]
- })
复制代码- # 线性插值
- df_linear = df.interpolate()
- # 时间序列数据的插值
- # 如果有日期时间索引,可以使用时间插值
- df_time = df.interpolate(method='time')
- # 多项式插值
- df_poly = df.interpolate(method='polynomial', order=2)
复制代码
示例:处理泰坦尼克号数据集中的缺失值
让我们继续使用泰坦尼克号数据集来处理缺失值:
- # 加载数据
- df = pd.read_csv('https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv')
- # 检查缺失值
- print("缺失值统计:")
- print(df.isnull().sum())
- # 处理Age列的缺失值:使用中位数填充
- df['Age'].fillna(df['Age'].median(), inplace=True)
- # 处理Cabin列的缺失值:填充为'Unknown'
- df['Cabin'].fillna('Unknown', inplace=True)
- # 处理Embarked列的缺失值:使用众数填充
- df['Embarked'].fillna(df['Embarked'].mode()[0], inplace=True)
- # 再次检查缺失值
- print("\n处理后的缺失值统计:")
- print(df.isnull().sum())
复制代码
处理重复值
重复值是数据清洗中另一个常见问题,它们可能会影响数据分析的结果。
检测重复值
- # 检查是否有重复行
- print(df.duplicated().sum())
- # 查看重复行
- print(df[df.duplicated()])
- # 基于特定列检查重复值
- print(df.duplicated(subset=['Name']).sum())
复制代码
处理重复值
- # 删除所有重复行
- df_drop_duplicates = df.drop_duplicates()
- # 删除基于特定列的重复行,保留第一个出现的行
- df_drop_subset_first = df.drop_duplicates(subset=['Name'], keep='first')
- # 删除基于特定列的重复行,保留最后一个出现的行
- df_drop_subset_last = df.drop_duplicates(subset=['Name'], keep='last')
- # 删除所有重复行(不保留任何重复行)
- df_drop_all = df.drop_duplicates(subset=['Name'], keep=False)
复制代码
示例:处理重复值
- # 创建一个包含重复值的示例数据框
- data = {'Name': ['Tom', 'Nick', 'John', 'Tom', 'Alice', 'Nick'],
- 'Age': [20, 21, 19, 20, 22, 21],
- 'Score': [85.5, 90.0, 76.5, 85.5, 88.0, 90.0]}
- df = pd.DataFrame(data)
- print("原始数据:")
- print(df)
- # 检查重复行
- print("\n重复行数量:", df.duplicated().sum())
- # 删除重复行
- df_clean = df.drop_duplicates()
- print("\n删除重复行后的数据:")
- print(df_clean)
复制代码
数据类型转换
正确的数据类型对于数据分析至关重要。在数据清洗过程中,我们经常需要转换列的数据类型。
查看数据类型
- # 查看各列的数据类型
- print(df.dtypes)
- # 查看数据类型的唯一值
- for col in df.columns:
- print(f"{col}: {df[col].dtype}")
复制代码
转换数据类型
- # 转换为数值类型
- df['Age'] = pd.to_numeric(df['Age'], errors='coerce') # errors='coerce'将无法转换的值设为NaN
- # 转换为整数类型
- df['Age'] = df['Age'].astype(int)
- # 转换为浮点数类型
- df['Score'] = df['Score'].astype(float)
- # 转换为字符串类型
- df['Name'] = df['Name'].astype(str)
- # 转换为日期时间类型
- df['Date'] = pd.to_datetime(df['Date'])
- # 转换为分类类型(适用于具有有限个唯一值的列)
- df['Gender'] = df['Gender'].astype('category')
复制代码
处理混合类型
有时,一列中可能包含混合的数据类型,这需要我们进行特殊处理。
- # 假设Price列包含字符串和数值,如"$100", "200", "¥300"
- # 首先转换为字符串,然后移除非数字字符,最后转换为数值
- df['Price'] = df['Price'].astype(str).str.replace(r'[^\d.]', '', regex=True)
- df['Price'] = pd.to_numeric(df['Price'], errors='coerce')
复制代码
示例:数据类型转换
- # 创建一个包含混合数据类型的示例数据框
- data = {'Name': ['Tom', 'Nick', 'John'],
- 'Age': ['20', '21', '19'], # 字符串类型的年龄
- 'Score': ['85.5', '90.0', '76.5'], # 字符串类型的分数
- 'Date': ['2021-01-01', '2021-02-01', '2021-03-01'], # 字符串类型的日期
- 'Member': ['Yes', 'No', 'Yes']} # 字符串类型的布尔值
- df = pd.DataFrame(data)
- print("原始数据类型:")
- print(df.dtypes)
- # 转换数据类型
- df['Age'] = df['Age'].astype(int)
- df['Score'] = df['Score'].astype(float)
- df['Date'] = pd.to_datetime(df['Date'])
- df['Member'] = df['Member'].map({'Yes': True, 'No': False})
- print("\n转换后的数据类型:")
- print(df.dtypes)
复制代码
异常值处理
异常值是数据集中与其他观测值显著不同的数据点。它们可能是测量错误、数据录入错误或真正的异常情况。处理异常值是数据清洗的重要一步。
检测异常值
- # 查看数值列的统计摘要
- print(df.describe())
- # 计算四分位数和IQR(四分位距)
- Q1 = df['Age'].quantile(0.25)
- Q3 = df['Age'].quantile(0.75)
- IQR = Q3 - Q1
- # 定义异常值的边界
- lower_bound = Q1 - 1.5 * IQR
- upper_bound = Q3 + 1.5 * IQR
- # 找出异常值
- outliers = df[(df['Age'] < lower_bound) | (df['Age'] > upper_bound)]
- print("Age列的异常值:")
- print(outliers)
复制代码- import matplotlib.pyplot as plt
- import seaborn as sns
- # 箱线图
- plt.figure(figsize=(10, 6))
- sns.boxplot(x=df['Age'])
- plt.title('Age列的箱线图')
- plt.show()
- # 直方图
- plt.figure(figsize=(10, 6))
- plt.hist(df['Age'], bins=30)
- plt.title('Age列的直方图')
- plt.show()
- # 散点图
- plt.figure(figsize=(10, 6))
- plt.scatter(df.index, df['Age'])
- plt.title('Age列的散点图')
- plt.show()
复制代码
处理异常值
- # 删除异常值
- df_no_outliers = df[(df['Age'] >= lower_bound) & (df['Age'] <= upper_bound)]
复制代码- # 用边界值替换异常值(截尾)
- df_capped = df.copy()
- df_capped['Age'] = np.where(df_capped['Age'] < lower_bound, lower_bound,
- np.where(df_capped['Age'] > upper_bound, upper_bound, df_capped['Age']))
- # 用中位数替换异常值
- median_age = df['Age'].median()
- df_median_replaced = df.copy()
- df_median_replaced['Age'] = np.where((df_median_replaced['Age'] < lower_bound) |
- (df_median_replaced['Age'] > upper_bound),
- median_age, df_median_replaced['Age'])
复制代码- from scipy import stats
- # 计算Z-score
- z_scores = np.abs(stats.zscore(df['Age']))
- # 定义阈值(通常为3)
- threshold = 3
- # 找出异常值
- outliers = df[z_scores > threshold]
- print("使用Z-score方法检测到的异常值:")
- print(outliers)
- # 删除异常值
- df_no_z_outliers = df[z_scores <= threshold]
复制代码
示例:处理异常值
- # 创建一个包含异常值的示例数据框
- np.random.seed(42)
- data = {'Name': ['Tom', 'Nick', 'John', 'Alice', 'Bob', 'Charlie', 'David', 'Eva'],
- 'Age': [20, 21, 19, 22, 20, 150, 18, 21], # 150是一个明显的异常值
- 'Score': [85.5, 90.0, 76.5, 88.0, 85.5, 92.0, 79.5, 90.0]}
- df = pd.DataFrame(data)
- print("原始数据:")
- print(df)
- # 使用箱线图可视化Age列
- plt.figure(figsize=(10, 6))
- sns.boxplot(x=df['Age'])
- plt.title('Age列的箱线图')
- plt.show()
- # 使用IQR方法检测异常值
- Q1 = df['Age'].quantile(0.25)
- Q3 = df['Age'].quantile(0.75)
- IQR = Q3 - Q1
- lower_bound = Q1 - 1.5 * IQR
- upper_bound = Q3 + 1.5 * IQR
- outliers = df[(df['Age'] < lower_bound) | (df['Age'] > upper_bound)]
- print("\n检测到的异常值:")
- print(outliers)
- # 用中位数替换异常值
- median_age = df['Age'].median()
- df_clean = df.copy()
- df_clean['Age'] = np.where((df_clean['Age'] < lower_bound) | (df_clean['Age'] > upper_bound),
- median_age, df_clean['Age'])
- print("\n处理异常值后的数据:")
- print(df_clean)
复制代码
字符串处理
文本数据清洗是数据预处理的重要组成部分。pandas提供了强大的字符串处理功能,可以帮助我们高效地清洗文本数据。
基本字符串操作
- # 转换为小写
- df['Name'] = df['Name'].str.lower()
- # 转换为大写
- df['Name'] = df['Name'].str.upper()
- # 首字母大写
- df['Name'] = df['Name'].str.title()
- # 去除前后空格
- df['Name'] = df['Name'].str.strip()
- # 去除所有空格
- df['Name'] = df['Name'].str.replace(' ', '')
- # 替换子字符串
- df['Name'] = df['Name'].str.replace('Mr.', 'Mister')
复制代码
高级字符串操作
- # 提取数字
- df['Numbers'] = df['Text'].str.extract('(\d+)')
- # 提取特定模式的文本
- df['Email'] = df['Text'].str.extract('([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})')
- # 分割字符串
- df['First_Name'] = df['Full_Name'].str.split(' ').str[0]
- df['Last_Name'] = df['Full_Name'].str.split(' ').str[-1]
- # 检查字符串是否包含特定子字符串
- df['Has_Mr'] = df['Name'].str.contains('Mr', case=False)
- # 检查字符串是否以特定子字符串开头
- df['Starts_With_A'] = df['Name'].str.startswith('A')
- # 检查字符串是否以特定子字符串结尾
- df['Ends_With_E'] = df['Name'].str.endswith('e')
- # 计算字符串长度
- df['Name_Length'] = df['Name'].str.len()
复制代码
正则表达式应用
- # 使用正则表达式进行复杂匹配
- # 提取所有大写字母
- df['Uppercase'] = df['Text'].str.findall('[A-Z]')
- # 提取所有单词
- df['Words'] = df['Text'].str.findall('\b\w+\b')
- # 替换所有非字母字符
- df['Letters_Only'] = df['Text'].str.replace('[^a-zA-Z]', ' ')
- # 提取连续的数字
- df['Numbers'] = df['Text'].str.extract('(\d+)')
复制代码
示例:清洗文本数据
- # 创建一个包含脏文本数据的示例数据框
- data = {'Full_Name': [' Mr. John Doe ', ' Ms. Jane Smith ', 'Dr. Robert Johnson', 'mrs. Emily Davis'],
- 'Email': ['john.doe@example.com', 'jane.smith@example.com', 'invalid-email', 'emily.davis@example.com'],
- 'Phone': ['(123) 456-7890', '123.456.7890', '1234567890', '123-456-7890'],
- 'Address': ['123 Main St, Anytown, USA', '456 Oak Ave, Somewhere, Canada', '789 Pine Rd, Nowhere, UK', '']}
- df = pd.DataFrame(data)
- print("原始数据:")
- print(df)
- # 清洗Full_Name列
- df['Full_Name'] = df['Full_Name'].str.strip() # 去除前后空格
- df['Title'] = df['Full_Name'].str.extract('(Mr|Ms|Dr|Mrs)\.', flags=re.IGNORECASE) # 提取称谓
- df['Title'] = df['Title'].str.title() # 首字母大写
- df['First_Name'] = df['Full_Name'].str.split().str[1] # 提取名字
- df['Last_Name'] = df['Full_Name'].str.split().str[2] # 提取姓氏
- # 清洗Email列
- df['Valid_Email'] = df['Email'].str.contains('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', regex=True)
- # 清洗Phone列
- df['Phone'] = df['Phone'].str.replace('[^\d]', '', regex=True) # 移除非数字字符
- df['Phone'] = df['Phone'].str.replace('^(\d{3})(\d{3})(\d{4})$', r'(\1) \2-\3', regex=True) # 格式化为(123) 456-7890
- # 清洗Address列
- df['Address'] = df['Address'].replace('', np.nan) # 将空字符串替换为NaN
- print("\n清洗后的数据:")
- print(df[['Title', 'First_Name', 'Last_Name', 'Email', 'Valid_Email', 'Phone', 'Address']])
复制代码
数据转换
数据转换是数据清洗过程中的重要环节,包括标准化、归一化、编码分类变量等操作,使数据适合于分析和建模。
标准化和归一化
标准化将数据转换为均值为0,标准差为1的分布。
- from sklearn.preprocessing import StandardScaler
- # 创建标准化器
- scaler = StandardScaler()
- # 标准化数值列
- df['Age_Standardized'] = scaler.fit_transform(df[['Age']])
- # 或者手动计算
- df['Age_Standardized_Manual'] = (df['Age'] - df['Age'].mean()) / df['Age'].std()
复制代码
归一化将数据缩放到[0, 1]区间。
- from sklearn.preprocessing import MinMaxScaler
- # 创建归一化器
- scaler = MinMaxScaler()
- # 归一化数值列
- df['Age_Normalized'] = scaler.fit_transform(df[['Age']])
- # 或者手动计算
- df['Age_Normalized_Manual'] = (df['Age'] - df['Age'].min()) / (df['Age'].max() - df['Age'].min())
复制代码
编码分类变量
标签编码将每个类别映射到一个整数。
- from sklearn.preprocessing import LabelEncoder
- # 创建标签编码器
- le = LabelEncoder()
- # 对分类列进行标签编码
- df['Gender_Encoded'] = le.fit_transform(df['Gender'])
- # 或者使用pandas的factorize方法
- df['Gender_Encoded'] = pd.factorize(df['Gender'])[0]
复制代码
独热编码为每个类别创建一个新的二进制列。
- # 使用pandas的get_dummies方法
- one_hot = pd.get_dummies(df['Gender'], prefix='Gender')
- df = pd.concat([df, one_hot], axis=1)
- # 或者使用sklearn的OneHotEncoder
- from sklearn.preprocessing import OneHotEncoder
- # 创建独热编码器
- encoder = OneHotEncoder(sparse=False)
- # 对分类列进行独热编码
- one_hot = encoder.fit_transform(df[['Gender']])
- # 创建DataFrame
- one_hot_df = pd.DataFrame(one_hot, columns=encoder.get_feature_names(['Gender']))
- # 合并到原始DataFrame
- df = pd.concat([df, one_hot_df], axis=1)
复制代码
分箱(Binning)
分箱是将连续变量转换为分类变量的过程。
- # 等宽分箱
- df['Age_Bin'] = pd.cut(df['Age'], bins=3, labels=['Young', 'Middle', 'Old'])
- # 等频分箱
- df['Age_Bin'] = pd.qcut(df['Age'], q=3, labels=['Young', 'Middle', 'Old'])
- # 自定义分箱
- bins = [0, 18, 35, 60, 100]
- labels = ['Child', 'Young Adult', 'Adult', 'Senior']
- df['Age_Bin'] = pd.cut(df['Age'], bins=bins, labels=labels)
复制代码
特征工程
- # 从日期特征中提取年、月、日
- df['Year'] = df['Date'].dt.year
- df['Month'] = df['Date'].dt.month
- df['Day'] = df['Date'].dt.day
- df['DayOfWeek'] = df['Date'].dt.dayofweek
- # 从文本特征中提取信息
- df['Name_Length'] = df['Name'].str.len()
- df['Name_Words'] = df['Name'].str.split().str.len()
- # 创建交互特征
- df['Age_Score_Interaction'] = df['Age'] * df['Score']
复制代码- # 对数变换(常用于处理偏态分布)
- df['Age_Log'] = np.log(df['Age'])
- # 平方根变换
- df['Age_Sqrt'] = np.sqrt(df['Age'])
- # 平方变换
- df['Age_Squared'] = df['Age'] ** 2
复制代码
示例:数据转换
- # 创建一个示例数据框
- data = {'Name': ['Tom', 'Nick', 'John', 'Alice', 'Bob'],
- 'Age': [20, 21, 19, 22, 20],
- 'Gender': ['Male', 'Male', 'Male', 'Female', 'Male'],
- 'Score': [85.5, 90.0, 76.5, 88.0, 85.5],
- 'Date': ['2021-01-01', '2021-02-01', '2021-03-01', '2021-04-01', '2021-05-01']}
- df = pd.DataFrame(data)
- # 转换日期列
- df['Date'] = pd.to_datetime(df['Date'])
- print("原始数据:")
- print(df)
- # 标准化Age列
- df['Age_Standardized'] = (df['Age'] - df['Age'].mean()) / df['Age'].std()
- # 归一化Score列
- df['Score_Normalized'] = (df['Score'] - df['Score'].min()) / (df['Score'].max() - df['Score'].min())
- # 对Gender列进行独热编码
- one_hot = pd.get_dummies(df['Gender'], prefix='Gender')
- df = pd.concat([df, one_hot], axis=1)
- # 从Date列提取特征
- df['Year'] = df['Date'].dt.year
- df['Month'] = df['Date'].dt.month
- df['Day'] = df['Date'].dt.day
- df['DayOfWeek'] = df['Date'].dt.dayofweek
- # 创建Age的分箱
- df['Age_Bin'] = pd.cut(df['Age'], bins=[0, 18, 25, 100], labels=['Teen', 'Young Adult', 'Adult'])
- # 创建交互特征
- df['Age_Score_Interaction'] = df['Age'] * df['Score']
- print("\n转换后的数据:")
- print(df)
复制代码
实战案例:一个完整的数据清洗示例
让我们通过一个完整的例子来应用我们学到的数据清洗技能。我们将使用一个模拟的电子商务数据集,该数据集包含客户信息、订单详情和产品信息。
1. 加载数据并初步检查
- # 创建模拟数据
- np.random.seed(42)
- # 客户数据
- customers = pd.DataFrame({
- 'CustomerID': range(1, 1001),
- 'Name': [f'Customer_{i}' for i in range(1, 1001)],
- 'Age': np.random.randint(18, 70, 1000),
- 'Gender': np.random.choice(['Male', 'Female', 'Other'], 1000),
- 'Email': [f'customer{i}@example.com' for i in range(1, 1001)],
- 'RegistrationDate': pd.date_range('2020-01-01', periods=1000, freq='D')
- })
- # 订单数据
- orders = pd.DataFrame({
- 'OrderID': range(1, 2001),
- 'CustomerID': np.random.randint(1, 1001, 2000),
- 'OrderDate': pd.date_range('2021-01-01', periods=2000, freq='6H'),
- 'ProductID': np.random.randint(1, 101, 2000),
- 'Quantity': np.random.randint(1, 10, 2000),
- 'Price': np.round(np.random.uniform(10, 500, 2000), 2)
- })
- # 产品数据
- products = pd.DataFrame({
- 'ProductID': range(1, 101),
- 'ProductName': [f'Product_{i}' for i in range(1, 101)],
- 'Category': np.random.choice(['Electronics', 'Clothing', 'Books', 'Home', 'Sports'], 100),
- 'Description': [f'Description for Product_{i}' for i in range(1, 101)]
- })
- # 引入一些数据质量问题
- # 在customers中引入一些缺失值
- customers.loc[np.random.choice(customers.index, 50, replace=False), 'Age'] = np.nan
- customers.loc[np.random.choice(customers.index, 30, replace=False), 'Gender'] = np.nan
- # 在orders中引入一些重复行
- orders = pd.concat([orders, orders.sample(50)], ignore_index=True)
- # 在orders中引入一些异常值
- orders.loc[np.random.choice(orders.index, 20, replace=False), 'Price'] = np.random.uniform(1000, 5000, 20)
- orders.loc[np.random.choice(orders.index, 10, replace=False), 'Quantity'] = np.random.randint(50, 100, 10)
- # 在products中引入一些不一致的产品名称
- products.loc[np.random.choice(products.index, 10, replace=False), 'ProductName'] = products.loc[np.random.choice(products.index, 10, replace=False), 'ProductName'].str.lower()
- # 保存数据到CSV文件
- customers.to_csv('customers.csv', index=False)
- orders.to_csv('orders.csv', index=False)
- products.to_csv('products.csv', index=False)
复制代码- # 加载数据
- customers = pd.read_csv('customers.csv')
- orders = pd.read_csv('orders.csv')
- products = pd.read_csv('products.csv')
- # 初步检查
- print("Customers数据维度:", customers.shape)
- print("\nCustomers前5行:")
- print(customers.head())
- print("\nCustomers数据信息:")
- customers.info()
- print("\nOrders数据维度:", orders.shape)
- print("\nOrders前5行:")
- print(orders.head())
- print("\nOrders数据信息:")
- orders.info()
- print("\nProducts数据维度:", products.shape)
- print("\nProducts前5行:")
- print(products.head())
- print("\nProducts数据信息:")
- products.info()
复制代码
2. 清洗Customers数据
- # 处理缺失值
- print("\nCustomers缺失值统计:")
- print(customers.isnull().sum())
- # 用中位数填充Age的缺失值
- customers['Age'].fillna(customers['Age'].median(), inplace=True)
- # 用众数填充Gender的缺失值
- customers['Gender'].fillna(customers['Gender'].mode()[0], inplace=True)
- # 检查处理后的缺失值
- print("\n处理后的Customers缺失值统计:")
- print(customers.isnull().sum())
- # 处理日期列
- customers['RegistrationDate'] = pd.to_datetime(customers['RegistrationDate'])
- # 检查重复值
- print("\nCustomers重复行数量:", customers.duplicated().sum())
- # 标准化Name列
- customers['Name'] = customers['Name'].str.title()
- # 验证Email格式
- import re
- email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
- customers['ValidEmail'] = customers['Email'].str.match(email_pattern)
- # 检查清洗后的数据
- print("\n清洗后的Customers数据:")
- print(customers.head())
- print("\nCustomers数据信息:")
- customers.info()
复制代码
3. 清洗Orders数据
- # 处理重复值
- print("\nOrders重复行数量:", orders.duplicated().sum())
- # 删除重复行
- orders.drop_duplicates(inplace=True)
- # 检查处理后的重复值
- print("\n处理后的Orders重复行数量:", orders.duplicated().sum())
- # 处理日期列
- orders['OrderDate'] = pd.to_datetime(orders['OrderDate'])
- # 检查异常值
- # 使用箱线图检查Price列
- plt.figure(figsize=(10, 6))
- sns.boxplot(x=orders['Price'])
- plt.title('Price列的箱线图')
- plt.show()
- # 使用箱线图检查Quantity列
- plt.figure(figsize=(10, 6))
- sns.boxplot(x=orders['Quantity'])
- plt.title('Quantity列的箱线图')
- plt.show()
- # 使用IQR方法处理Price列的异常值
- Q1 = orders['Price'].quantile(0.25)
- Q3 = orders['Price'].quantile(0.75)
- IQR = Q3 - Q1
- lower_bound = Q1 - 1.5 * IQR
- upper_bound = Q3 + 1.5 * IQR
- print(f"\nPrice列的异常值边界: {lower_bound:.2f} - {upper_bound:.2f}")
- price_outliers = orders[(orders['Price'] < lower_bound) | (orders['Price'] > upper_bound)]
- print(f"Price列的异常值数量: {len(price_outliers)}")
- # 用边界值替换Price列的异常值
- orders['Price'] = np.where(orders['Price'] < lower_bound, lower_bound,
- np.where(orders['Price'] > upper_bound, upper_bound, orders['Price']))
- # 使用IQR方法处理Quantity列的异常值
- Q1 = orders['Quantity'].quantile(0.25)
- Q3 = orders['Quantity'].quantile(0.75)
- IQR = Q3 - Q1
- lower_bound = Q1 - 1.5 * IQR
- upper_bound = Q3 + 1.5 * IQR
- print(f"\nQuantity列的异常值边界: {lower_bound:.2f} - {upper_bound:.2f}")
- quantity_outliers = orders[(orders['Quantity'] < lower_bound) | (orders['Quantity'] > upper_bound)]
- print(f"Quantity列的异常值数量: {len(quantity_outliers)}")
- # 用边界值替换Quantity列的异常值
- orders['Quantity'] = np.where(orders['Quantity'] < lower_bound, lower_bound,
- np.where(orders['Quantity'] > upper_bound, upper_bound, orders['Quantity']))
- # 创建TotalAmount列
- orders['TotalAmount'] = orders['Quantity'] * orders['Price']
- # 检查清洗后的数据
- print("\n清洗后的Orders数据:")
- print(orders.head())
- print("\nOrders数据信息:")
- orders.info()
复制代码
4. 清洗Products数据
- # 标准化ProductName列
- products['ProductName'] = products['ProductName'].str.title()
- # 检查重复值
- print("\nProducts重复行数量:", products.duplicated().sum())
- # 检查ProductID是否唯一
- print("\nProductID是否唯一:", products['ProductID'].is_unique)
- # 检查Category列的唯一值
- print("\nCategory列的唯一值:")
- print(products['Category'].value_counts())
- # 标准化Category列
- products['Category'] = products['Category'].str.title()
- # 检查清洗后的数据
- print("\n清洗后的Products数据:")
- print(products.head())
- print("\nProducts数据信息:")
- products.info())
复制代码
5. 合并数据集
- # 合并orders和customers
- order_customer = pd.merge(orders, customers, on='CustomerID', how='left')
- # 合并order_customer和products
- full_data = pd.merge(order_customer, products, on='ProductID', how='left')
- # 检查合并后的数据
- print("\n合并后的数据维度:", full_data.shape)
- print("\n合并后的数据前5行:")
- print(full_data.head())
- print("\n合并后的数据信息:")
- full_data.info())
- # 检查合并后的缺失值
- print("\n合并后的缺失值统计:")
- print(full_data.isnull().sum())
复制代码
6. 特征工程和数据分析
- # 从OrderDate提取特征
- full_data['OrderYear'] = full_data['OrderDate'].dt.year
- full_data['OrderMonth'] = full_data['OrderDate'].dt.month
- full_data['OrderDay'] = full_data['OrderDate'].dt.day
- full_data['OrderDayOfWeek'] = full_data['OrderDate'].dt.dayofweek
- full_data['OrderHour'] = full_data['OrderDate'].dt.hour
- # 从RegistrationDate提取特征
- full_data['RegistrationYear'] = full_data['RegistrationDate'].dt.year
- full_data['RegistrationMonth'] = full_data['RegistrationDate'].dt.month
- # 计算客户年龄(基于注册日期)
- full_data['AgeAtRegistration'] = full_data['RegistrationYear'] - full_data['Age']
- # 创建Age分箱
- full_data['AgeGroup'] = pd.cut(full_data['Age'], bins=[0, 25, 35, 50, 100], labels=['18-25', '26-35', '36-50', '50+'])
- # 创建Price分箱
- full_data['PriceRange'] = pd.cut(full_data['Price'], bins=[0, 50, 100, 200, 500], labels=['0-50', '51-100', '101-200', '201-500'])
- # 检查特征工程后的数据
- print("\n特征工程后的数据:")
- print(full_data.head())
- # 分析不同性别的购买行为
- gender_analysis = full_data.groupby('Gender').agg({
- 'TotalAmount': ['mean', 'sum', 'count'],
- 'Quantity': 'mean'
- })
- print("\n不同性别的购买行为分析:")
- print(gender_analysis)
- # 分析不同年龄组的购买行为
- age_analysis = full_data.groupby('AgeGroup').agg({
- 'TotalAmount': ['mean', 'sum', 'count'],
- 'Quantity': 'mean'
- })
- print("\n不同年龄组的购买行为分析:")
- print(age_analysis)
- # 分析不同产品类别的销售情况
- category_analysis = full_data.groupby('Category').agg({
- 'TotalAmount': ['mean', 'sum', 'count'],
- 'Quantity': 'mean'
- })
- print("\n不同产品类别的销售情况分析:")
- print(category_analysis)
- # 分析不同时间段的销售情况
- month_analysis = full_data.groupby('OrderMonth').agg({
- 'TotalAmount': ['mean', 'sum', 'count'],
- 'Quantity': 'mean'
- })
- print("\n不同月份的销售情况分析:")
- print(month_analysis)
- # 可视化分析
- # 不同性别的总销售额
- plt.figure(figsize=(10, 6))
- sns.barplot(x=full_data['Gender'], y=full_data['TotalAmount'], estimator=sum)
- plt.title('不同性别的总销售额')
- plt.show()
- # 不同年龄组的总销售额
- plt.figure(figsize=(10, 6))
- sns.barplot(x=full_data['AgeGroup'], y=full_data['TotalAmount'], estimator=sum)
- plt.title('不同年龄组的总销售额')
- plt.show()
- # 不同产品类别的总销售额
- plt.figure(figsize=(10, 6))
- sns.barplot(x=full_data['Category'], y=full_data['TotalAmount'], estimator=sum)
- plt.title('不同产品类别的总销售额')
- plt.show()
- # 不同月份的总销售额
- plt.figure(figsize=(10, 6))
- sns.barplot(x=full_data['OrderMonth'], y=full_data['TotalAmount'], estimator=sum)
- plt.title('不同月份的总销售额')
- plt.show()
复制代码
总结与最佳实践
通过本教程,我们系统地学习了使用pandas进行数据清洗的各种技能,从处理缺失值、重复值,到数据类型转换、异常值处理,再到字符串处理和数据转换。最后,我们通过一个完整的实战案例,将这些技能应用到实际的数据清洗和分析过程中。
数据清洗的最佳实践
1. 了解你的数据:在开始清洗之前,先通过描述性统计和可视化方法深入了解数据的特征和问题。
2. 制定清洗策略:根据数据的特点和分析目标,制定合适的数据清洗策略。不是所有的缺失值都需要填充,不是所有的异常值都需要处理。
3. 记录清洗过程:记录每一步清洗操作,以便于回溯和复现。可以考虑使用函数或类来封装清洗逻辑。
4. 保持原始数据:在清洗数据时,最好保留原始数据的副本,以便在需要时进行比较或回滚。
5. 验证清洗结果:每完成一步清洗操作,都要验证结果是否符合预期。
6. 自动化清洗流程:对于需要定期处理的数据,考虑将清洗流程自动化,提高工作效率。
了解你的数据:在开始清洗之前,先通过描述性统计和可视化方法深入了解数据的特征和问题。
制定清洗策略:根据数据的特点和分析目标,制定合适的数据清洗策略。不是所有的缺失值都需要填充,不是所有的异常值都需要处理。
记录清洗过程:记录每一步清洗操作,以便于回溯和复现。可以考虑使用函数或类来封装清洗逻辑。
保持原始数据:在清洗数据时,最好保留原始数据的副本,以便在需要时进行比较或回滚。
验证清洗结果:每完成一步清洗操作,都要验证结果是否符合预期。
自动化清洗流程:对于需要定期处理的数据,考虑将清洗流程自动化,提高工作效率。
提高数据清洗效率的技巧
1. 使用向量化操作:pandas的向量化操作比循环更高效,尽量使用内置的向量化函数。
2. 使用apply函数:对于复杂的列操作,可以使用apply函数,它比循环更高效。
3. 使用链式操作:pandas支持方法链,可以将多个操作链接在一起,使代码更简洁高效。
4. 使用适当的数据结构:根据数据的特点选择合适的数据结构,例如对于分类数据使用category类型可以节省内存。
5. 并行处理:对于大型数据集,可以考虑使用并行处理技术,如Dask或Modin。
使用向量化操作:pandas的向量化操作比循环更高效,尽量使用内置的向量化函数。
使用apply函数:对于复杂的列操作,可以使用apply函数,它比循环更高效。
使用链式操作:pandas支持方法链,可以将多个操作链接在一起,使代码更简洁高效。
使用适当的数据结构:根据数据的特点选择合适的数据结构,例如对于分类数据使用category类型可以节省内存。
并行处理:对于大型数据集,可以考虑使用并行处理技术,如Dask或Modin。
持续学习
数据清洗是一个不断学习和实践的过程。随着数据处理需求的不断变化,新的工具和技术也在不断涌现。保持学习的态度,关注最新的数据处理技术和最佳实践,将帮助你更高效地应对各种数据清洗挑战。
希望本教程能帮助你掌握pandas数据清洗的基本技能,为你的数据分析工作打下坚实的基础。记住,数据清洗是数据分析过程中不可或缺的一步,掌握好这项技能,将大大提高你的工作效率和分析结果的可靠性。
版权声明
1、转载或引用本网站内容(pandas数据清洗入门教程新手必备的数据处理技能从零开始学起轻松应对数据分析中的各种清洗问题提升工作效率)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://www.pixtech.org/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://www.pixtech.org/thread-31926-1-1.html
|
|