|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在数据科学和分析领域,Pandas已经成为Python生态系统中最核心的库之一。作为Pandas库中最重要的数据结构,DataFrame提供了强大而灵活的数据操作能力,使得数据清洗、转换、分析和可视化变得更加高效。本文将全面解析Pandas DataFrame数据结构,从基础概念到高级技巧,帮助你掌握这一强大工具,成为数据处理高手。
Pandas DataFrame基础概念
什么是DataFrame
DataFrame是Pandas库中的一个二维标记数据结构,类似于SQL中的表或Excel中的电子表格。它由一组有序的列组成,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,可以看作是共享相同索引的Series对象的集合。
- import pandas as pd
- import numpy as np
- # 创建一个简单的DataFrame
- data = {
- '姓名': ['张三', '李四', '王五', '赵六'],
- '年龄': [25, 30, 35, 40],
- '城市': ['北京', '上海', '广州', '深圳'],
- '薪资': [8000, 12000, 15000, 20000]
- }
- df = pd.DataFrame(data)
- print(df)
复制代码
输出:
- 姓名 年龄 城市 薪资
- 0 张三 25 北京 8000
- 1 李四 30 上海 12000
- 2 王五 35 广州 15000
- 3 赵六 40 深圳 20000
复制代码
DataFrame与Series的关系
Series是Pandas中的一维标记数组,可以看作是DataFrame的一列。DataFrame可以看作是多个Series对象的集合,这些Series对象共享同一个索引。
- # 从DataFrame中提取一列作为Series
- salary_series = df['薪资']
- print(type(salary_series))
- print(salary_series)
复制代码
输出:
- <class 'pandas.core.series.Series'>
- 0 8000
- 1 12000
- 2 15000
- 3 20000
- Name: 薪资, dtype: int64
复制代码
DataFrame的基本属性
DataFrame有许多有用的属性,可以帮助我们了解数据的基本情况:
- # 查看DataFrame的形状(行数,列数)
- print("形状:", df.shape)
- # 查看列名
- print("列名:", df.columns.tolist())
- # 查看索引
- print("索引:", df.index.tolist())
- # 查看数据类型
- print("数据类型:\n", df.dtypes)
- # 查看数据的统计摘要
- print("统计摘要:\n", df.describe())
- # 查看前几行数据(默认5行)
- print("前两行数据:\n", df.head(2))
- # 查看后几行数据(默认5行)
- print("后两行数据:\n", df.tail(2))
- # 获取数据的转置
- print("转置:\n", df.T)
复制代码
创建DataFrame的不同方法
从字典创建
最常用的创建DataFrame的方式是从字典创建,字典的键将成为列名,值将成为列数据。
- # 从字典创建DataFrame
- data = {
- '产品': ['A', 'B', 'C', 'D'],
- '销量': [100, 150, 200, 120],
- '价格': [10.5, 20.3, 15.8, 18.9]
- }
- df_from_dict = pd.DataFrame(data)
- print(df_from_dict)
复制代码
从列表创建
可以从嵌套列表创建DataFrame,需要单独指定列名。
- # 从列表创建DataFrame
- data = [
- ['A', 100, 10.5],
- ['B', 150, 20.3],
- ['C', 200, 15.8],
- ['D', 120, 18.9]
- ]
- columns = ['产品', '销量', '价格']
- df_from_list = pd.DataFrame(data, columns=columns)
- print(df_from_list)
复制代码
从NumPy数组创建
可以从NumPy数组创建DataFrame,这对于科学计算特别有用。
- # 从NumPy数组创建DataFrame
- array = np.random.rand(5, 4) # 创建一个5行4列的随机数数组
- columns = ['A', 'B', 'C', 'D']
- df_from_array = pd.DataFrame(array, columns=columns)
- print(df_from_array)
复制代码
从文件读取
Pandas支持从多种文件格式读取数据,包括CSV、Excel、JSON、SQL等。
- # 从CSV文件读取
- # df_csv = pd.read_csv('data.csv')
- # 从Excel文件读取
- # df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1')
- # 从JSON文件读取
- # df_json = pd.read_json('data.json')
- # 从SQL查询读取
- # import sqlite3
- # conn = sqlite3.connect('database.db')
- # df_sql = pd.read_sql('SELECT * FROM table_name', conn)
- # conn.close()
复制代码
DataFrame基本操作
查看数据
- # 创建一个示例DataFrame
- data = {
- '姓名': ['张三', '李四', '王五', '赵六', '钱七'],
- '年龄': [25, 30, 35, 40, 45],
- '城市': ['北京', '上海', '广州', '深圳', '杭州'],
- '薪资': [8000, 12000, 15000, 20000, 18000]
- }
- df = pd.DataFrame(data)
- # 查看前n行
- print(df.head(3))
- # 查看后n行
- print(df.tail(2))
- # 查看随机n行
- print(df.sample(2))
- # 查看基本信息
- print(df.info())
- # 查看描述性统计
- print(df.describe())
复制代码
选择数据
- # 选择单列 - 返回Series
- name_series = df['姓名']
- print(name_series)
- print(type(name_series))
- # 选择多列 - 返回DataFrame
- subset = df[['姓名', '薪资']]
- print(subset)
- print(type(subset))
复制代码- # 使用loc基于标签选择行
- print(df.loc[0]) # 选择第一行
- print(df.loc[0:2]) # 选择前三行
- # 使用iloc基于位置选择行
- print(df.iloc[0]) # 选择第一行
- print(df.iloc[0:3]) # 选择前三行
- # 条件选择
- high_salary = df[df['薪资'] > 15000]
- print(high_salary)
- # 多条件选择
- beijing_or_shanghai = df[(df['城市'] == '北京') | (df['城市'] == '上海')]
- print(beijing_or_shanghai)
- # 使用isin方法
- selected_cities = df[df['城市'].isin(['北京', '上海'])]
- print(selected_cities)
复制代码- # 使用at选择单个单元格(基于标签)
- print(df.at[1, '姓名']) # 输出: 李四
- # 使用iat选择单个单元格(基于位置)
- print(df.iat[1, 0]) # 输出: 李四
- # 使用loc选择单元格区域
- print(df.loc[1:3, ['姓名', '薪资']])
- # 使用iloc选择单元格区域
- print(df.iloc[1:4, [0, 3]])
复制代码
数据过滤
- # 单条件过滤
- age_above_30 = df[df['年龄'] > 30]
- print(age_above_30)
- # 多条件过滤 - 使用&(与)、|(或)、~(非)
- age_30_to_40 = df[(df['年龄'] >= 30) & (df['年龄'] <= 40)]
- print(age_30_to_40)
- # 使用query方法
- query_result = df.query('年龄 > 30 and 薪资 < 20000')
- print(query_result)
- # 使用字符串方法过滤
- name_starts_with_z = df[df['姓名'].str.startswith('张')]
- print(name_starts_with_z)
复制代码
数据排序
- # 按单列排序
- sorted_by_age = df.sort_values('年龄')
- print(sorted_by_age)
- # 按多列排序
- sorted_by_age_salary = df.sort_values(['年龄', '薪资'], ascending=[True, False])
- print(sorted_by_age_salary)
- # 按索引排序
- sorted_by_index = df.sort_index()
- print(sorted_by_index)
- # 设置新列并按其排序
- df['薪资等级'] = ['低', '中', '中', '高', '高']
- sorted_by_rank = df.sort_values('薪资等级')
- print(sorted_by_rank)
复制代码
数据清洗与预处理
处理缺失值
- # 创建带有缺失值的DataFrame
- data_with_nan = {
- 'A': [1, 2, np.nan, 4, 5],
- 'B': [np.nan, 2, 3, np.nan, 5],
- 'C': [1, 2, 3, 4, 5],
- 'D': ['a', 'b', np.nan, 'd', 'e']
- }
- df_nan = pd.DataFrame(data_with_nan)
- print(df_nan)
- # 检测缺失值
- print(df_nan.isna())
- # 统计每列的缺失值数量
- print(df_nan.isna().sum())
- # 删除含有缺失值的行
- df_dropped_rows = df_nan.dropna()
- print(df_dropped_rows)
- # 删除含有缺失值的列
- df_dropped_cols = df_nan.dropna(axis=1)
- print(df_dropped_cols)
- # 填充缺失值
- df_filled = df_nan.fillna(0)
- print(df_filled)
- # 使用列的平均值填充数值列
- df_nan['A'] = df_nan['A'].fillna(df_nan['A'].mean())
- print(df_nan)
- # 使用前一个值填充缺失值
- df_ffill = df_nan.fillna(method='ffill')
- print(df_ffill)
- # 使用后一个值填充缺失值
- df_bfill = df_nan.fillna(method='bfill')
- print(df_bfill)
- # 使用插值法填充缺失值
- df_interpolated = df_nan.copy()
- df_interpolated['A'] = df_interpolated['A'].interpolate()
- print(df_interpolated)
复制代码
数据类型转换
- # 创建示例DataFrame
- data_types = {
- 'ID': ['001', '002', '003', '004'],
- '价格': ['10.5', '20.3', '15.8', '18.9'],
- '数量': ['100', '150', '200', '120'],
- '日期': ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
- }
- df_types = pd.DataFrame(data_types)
- print(df_types.dtypes)
- # 转换数据类型
- df_types['价格'] = df_types['价格'].astype(float)
- df_types['数量'] = df_types['数量'].astype(int)
- df_types['日期'] = pd.to_datetime(df_types['日期'])
- print(df_types.dtypes)
- # 使用pd.to_numeric进行更安全的转换
- df_types['ID'] = pd.to_numeric(df_types['ID'], errors='ignore') # 如果转换失败则保持原样
- print(df_types.dtypes)
- # 分类类型转换 - 适用于重复的字符串值
- df_types['城市'] = ['北京', '上海', '北京', '上海']
- df_types['城市'] = df_types['城市'].astype('category')
- print(df_types['城市'].dtype)
复制代码
重复值处理
- # 创建带有重复值的DataFrame
- data_duplicates = {
- 'ID': [1, 2, 3, 4, 1, 2],
- '姓名': ['张三', '李四', '王五', '赵六', '张三', '李四'],
- '年龄': [25, 30, 35, 40, 25, 30]
- }
- df_duplicates = pd.DataFrame(data_duplicates)
- print(df_duplicates)
- # 检测重复行
- print(df_duplicates.duplicated())
- # 统计重复行数量
- print(df_duplicates.duplicated().sum())
- # 删除重复行
- df_no_duplicates = df_duplicates.drop_duplicates()
- print(df_no_duplicates)
- # 基于特定列删除重复行
- df_unique_id = df_duplicates.drop_duplicates(subset=['ID'])
- print(df_unique_id)
- # 保留最后一个出现的重复行
- df_keep_last = df_duplicates.drop_duplicates(keep='last')
- print(df_keep_last)
复制代码
异常值检测与处理
- # 创建包含异常值的DataFrame
- np.random.seed(42)
- data_outliers = {
- 'A': np.concatenate([np.random.normal(0, 1, 50), [10, -10]]),
- 'B': np.concatenate([np.random.normal(5, 2, 50), [20, -15]])
- }
- df_outliers = pd.DataFrame(data_outliers)
- # 使用箱线图检测异常值
- import matplotlib.pyplot as plt
- plt.figure(figsize=(10, 5))
- df_outliers.boxplot()
- plt.title('箱线图检测异常值')
- plt.show()
- # 使用Z-score检测异常值
- from scipy import stats
- z_scores = stats.zscore(df_outliers)
- abs_z_scores = np.abs(z_scores)
- outliers = (abs_z_scores > 3).any(axis=1)
- print("异常值索引:", np.where(outliers)[0])
- # 使用IQR方法检测异常值
- Q1 = df_outliers.quantile(0.25)
- Q3 = df_outliers.quantile(0.75)
- IQR = Q3 - Q1
- outliers_iqr = ((df_outliers < (Q1 - 1.5 * IQR)) | (df_outliers > (Q3 + 1.5 * IQR))).any(axis=1)
- print("IQR方法检测到的异常值索引:", np.where(outliers_iqr)[0])
- # 处理异常值 - 删除
- df_no_outliers = df_outliers[~outliers]
- print("删除异常值后的形状:", df_no_outliers.shape)
- # 处理异常值 - 替换为中位数
- df_replace_median = df_outliers.copy()
- for column in df_replace_median.columns:
- median = df_replace_median[column].median()
- df_replace_median.loc[outliers, column] = median
- print(df_replace_median.loc[outliers])
复制代码
数据转换与处理
添加/删除列
- # 创建示例DataFrame
- data = {
- '姓名': ['张三', '李四', '王五', '赵六'],
- '年龄': [25, 30, 35, 40],
- '城市': ['北京', '上海', '广州', '深圳']
- }
- df = pd.DataFrame(data)
- # 添加新列
- df['薪资'] = [8000, 12000, 15000, 20000]
- print(df)
- # 基于现有列创建新列
- df['年薪'] = df['薪资'] * 12
- print(df)
- # 使用assign方法添加列(不会修改原DataFrame)
- df_new = df.assign(月薪等级=['低', '中', '中', '高'])
- print(df_new)
- print("原DataFrame未改变:", df.columns.tolist())
- # 删除列
- df_dropped = df.drop('年薪', axis=1)
- print(df_dropped)
- # 使用del删除列
- df_copy = df.copy()
- del df_copy['年薪']
- print(df_copy)
- # 使用pop删除列并返回
- df_copy = df.copy()
- salary = df_copy.pop('薪资')
- print(df_copy)
- print("被删除的列:\n", salary)
复制代码
应用函数
- # 创建示例DataFrame
- data = {
- '姓名': ['张三', '李四', '王五', '赵六'],
- '年龄': [25, 30, 35, 40],
- '薪资': [8000, 12000, 15000, 20000]
- }
- df = pd.DataFrame(data)
- # 对列应用函数
- df['年龄平方'] = df['年龄'].apply(lambda x: x ** 2)
- print(df)
- # 对多列应用函数
- df['薪资等级'] = df.apply(lambda row: '高' if row['薪资'] > 15000 else ('中' if row['薪资'] > 10000 else '低'), axis=1)
- print(df)
- # 使用applymap对每个元素应用函数
- df_str = df.copy()
- df_str[['姓名', '薪资等级']] = df_str[['姓名', '薪资等级']].applymap(lambda x: str(x) + '!')
- print(df_str)
- # 使用map函数对Series进行转换
- df['城市代码'] = df['姓名'].map({'张三': 1001, '李四': 1002, '王五': 1003, '赵六': 1004})
- print(df)
- # 使用transform函数
- df['年龄差异'] = df['年龄'].transform(lambda x: x - x.mean())
- print(df)
- # 使用agg函数应用多个聚合函数
- print(df[['年龄', '薪资']].agg(['mean', 'max', 'min']))
复制代码
分组操作
- # 创建示例DataFrame
- data = {
- '部门': ['技术', '市场', '技术', '财务', '市场', '技术', '财务'],
- '姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九'],
- '年龄': [25, 30, 35, 40, 28, 32, 45],
- '薪资': [8000, 12000, 15000, 20000, 10000, 14000, 22000]
- }
- df = pd.DataFrame(data)
- # 按部门分组
- grouped = df.groupby('部门')
- # 计算每组的平均薪资
- avg_salary = grouped['薪资'].mean()
- print(avg_salary)
- # 计算每组的多个统计量
- stats = grouped['薪资'].agg(['mean', 'max', 'min', 'count'])
- print(stats)
- # 对多列应用不同的聚合函数
- multi_agg = grouped.agg({
- '年龄': ['mean', 'max'],
- '薪资': ['mean', 'sum', 'count']
- })
- print(multi_agg)
- # 自定义聚合函数
- def salary_range(x):
- return x.max() - x.min()
- range_result = grouped['薪资'].agg(salary_range)
- print(range_result)
- # 遍历分组
- for name, group in grouped:
- print(f"部门: {name}")
- print(group)
- print()
- # 使用transform进行分组转换
- df['部门平均薪资'] = grouped['薪资'].transform('mean')
- print(df)
- # 使用filter过滤分组
- tech_dept = grouped.filter(lambda x: x['部门'].iloc[0] == '技术')
- print(tech_dept)
复制代码
数据透视表
- # 创建示例DataFrame
- data = {
- '日期': pd.date_range('2023-01-01', periods=12),
- '产品': ['A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B'],
- '地区': ['北京', '北京', '上海', '上海', '广州', '广州', '北京', '北京', '上海', '上海', '广州', '广州'],
- '销售额': [100, 150, 200, 120, 180, 160, 110, 140, 210, 130, 190, 170],
- '成本': [60, 90, 120, 72, 108, 96, 66, 84, 126, 78, 114, 102]
- }
- df = pd.DataFrame(data)
- # 创建数据透视表
- pivot = pd.pivot_table(df,
- values='销售额',
- index='产品',
- columns='地区',
- aggfunc='sum')
- print(pivot)
- # 多值数据透视表
- pivot_multi = pd.pivot_table(df,
- values=['销售额', '成本'],
- index='产品',
- columns='地区',
- aggfunc='sum')
- print(pivot_multi)
- # 添加总计行和列
- pivot_with_totals = pd.pivot_table(df,
- values='销售额',
- index='产品',
- columns='地区',
- aggfunc='sum',
- margins=True,
- margins_name='总计')
- print(pivot_with_totals)
- # 多级索引数据透视表
- pivot_multi_index = pd.pivot_table(df,
- values='销售额',
- index=['产品', '地区'],
- aggfunc='sum')
- print(pivot_multi_index)
- # 使用多个聚合函数
- pivot_multi_agg = pd.pivot_table(df,
- values='销售额',
- index='产品',
- columns='地区',
- aggfunc=['sum', 'mean', 'count'])
- print(pivot_multi_agg)
复制代码
高级技巧
多级索引
- # 创建多级索引DataFrame
- arrays = [
- ['A', 'A', 'B', 'B', 'C', 'C'],
- ['X', 'Y', 'X', 'Y', 'X', 'Y']
- ]
- tuples = list(zip(*arrays))
- index = pd.MultiIndex.from_tuples(tuples, names=['一级', '二级'])
- df_multi = pd.DataFrame(np.random.randn(6, 2), index=index, columns=['值1', '值2'])
- print(df_multi)
- # 选择多级索引数据
- print(df_multi.loc['A'])
- print(df_multi.loc[('A', 'X')])
- print(df_multi.loc[:, '值1'])
- # 使用xs方法
- print(df_multi.xs('A', level='一级'))
- print(df_multi.xs('X', level='二级'))
- # 设置多级索引
- data = {
- '部门': ['技术', '技术', '市场', '市场', '财务', '财务'],
- '团队': ['前端', '后端', '推广', '销售', '会计', '审计'],
- '员工': ['张三', '李四', '王五', '赵六', '钱七', '孙八'],
- '薪资': [8000, 12000, 10000, 15000, 9000, 11000]
- }
- df = pd.DataFrame(data)
- df_multi = df.set_index(['部门', '团队'])
- print(df_multi)
- # 交换索引级别
- df_swapped = df_multi.swaplevel()
- print(df_swapped)
- # 排序索引
- df_sorted = df_multi.sort_index(level=0)
- print(df_sorted)
- # 重置索引
- df_reset = df_multi.reset_index()
- print(df_reset)
复制代码
时间序列处理
- # 创建时间序列数据
- dates = pd.date_range('20230101', periods=10)
- ts = pd.Series(np.random.randn(10), index=dates)
- print(ts)
- # 选择特定时间范围的数据
- print(ts['2023-01-03':'2023-01-07'])
- print(ts['2023-01']) # 选择整个月
- # 创建带有时间索引的DataFrame
- data = {
- '日期': pd.date_range('20230101', periods=30),
- '销售额': np.random.randint(100, 200, size=30),
- '成本': np.random.randint(50, 100, size=30)
- }
- df_ts = pd.DataFrame(data)
- df_ts.set_index('日期', inplace=True)
- print(df_ts.head())
- # 重采样
- # 按周重采样,计算每周的平均值
- weekly_avg = df_ts.resample('W').mean()
- print(weekly_avg)
- # 按月重采样,计算每月的总和
- monthly_sum = df_ts.resample('M').sum()
- print(monthly_sum)
- # 移动窗口
- # 计算7天移动平均
- df_ts['销售额_7天移动平均'] = df_ts['销售额'].rolling(window=7).mean()
- print(df_ts[['销售额', '销售额_7天移动平均']].head(10))
- # 计算扩展窗口统计
- df_ts['销售额累计最大'] = df_ts['销售额'].expanding().max()
- print(df_ts[['销售额', '销售额累计最大']].head())
- # 时间偏移
- df_ts['前一天销售额'] = df_ts['销售额'].shift(1)
- print(df_ts[['销售额', '前一天销售额']].head())
- # 计算变化率
- df_ts['销售额日变化率'] = df_ts['销售额'].pct_change()
- print(df_ts[['销售额', '销售额日变化率']].head())
- # 处理时区
- # 创建带时区的时间序列
- ts_utc = ts.tz_localize('UTC')
- print(ts_utc)
- # 转换时区
- ts_est = ts_utc.tz_convert('US/Eastern')
- print(ts_est)
复制代码
合并与连接
- # 创建示例DataFrame
- df1 = pd.DataFrame({
- 'ID': [1, 2, 3, 4],
- '姓名': ['张三', '李四', '王五', '赵六'],
- '年龄': [25, 30, 35, 40]
- })
- df2 = pd.DataFrame({
- 'ID': [1, 2, 3, 5],
- '薪资': [8000, 12000, 15000, 18000],
- '部门': ['技术', '市场', '财务', '人事']
- })
- # 内连接
- inner_merge = pd.merge(df1, df2, on='ID', how='inner')
- print("内连接:")
- print(inner_merge)
- # 左连接
- left_merge = pd.merge(df1, df2, on='ID', how='left')
- print("\n左连接:")
- print(left_merge)
- # 右连接
- right_merge = pd.merge(df1, df2, on='ID', how='right')
- print("\n右连接:")
- print(right_merge)
- # 外连接
- outer_merge = pd.merge(df1, df2, on='ID', how='outer')
- print("\n外连接:")
- print(outer_merge)
- # 基于多列连接
- df3 = pd.DataFrame({
- '姓名': ['张三', '李四', '王五', '赵六'],
- '年龄': [25, 30, 35, 40],
- '城市': ['北京', '上海', '广州', '深圳']
- })
- df4 = pd.DataFrame({
- '姓名': ['张三', '李四', '王五', '钱七'],
- '年龄': [25, 30, 35, 45],
- '薪资': [8000, 12000, 15000, 18000]
- })
- multi_merge = pd.merge(df3, df4, on=['姓名', '年龄'], how='inner')
- print("\n基于多列连接:")
- print(multi_merge)
- # 纵向拼接
- df5 = pd.DataFrame({
- 'ID': [5, 6, 7],
- '姓名': ['钱七', '孙八', '周九'],
- '年龄': [45, 50, 55]
- })
- concat_vertical = pd.concat([df1, df5], ignore_index=True)
- print("\n纵向拼接:")
- print(concat_vertical)
- # 横向拼接
- df6 = pd.DataFrame({
- '城市': ['北京', '上海', '广州', '深圳'],
- '学历': ['本科', '硕士', '本科', '博士']
- })
- concat_horizontal = pd.concat([df1, df6], axis=1)
- print("\n横向拼接:")
- print(concat_horizontal)
- # 使用join方法
- df7 = df1.set_index('ID')
- df8 = df2.set_index('ID')
- join_result = df7.join(df8, how='inner')
- print("\n使用join方法:")
- print(join_result)
复制代码
性能优化
- # 创建大型DataFrame
- large_df = pd.DataFrame({
- 'A': np.random.rand(100000),
- 'B': np.random.rand(100000),
- 'C': np.random.randint(0, 100, size=100000),
- 'D': np.random.choice(['X', 'Y', 'Z'], size=100000)
- })
- # 优化内存使用
- # 查看当前内存使用
- print("原始内存使用:")
- print(large_df.memory_usage(deep=True))
- # 降级数值类型
- large_df['A'] = pd.to_numeric(large_df['A'], downcast='float')
- large_df['B'] = pd.to_numeric(large_df['B'], downcast='float')
- large_df['C'] = pd.to_numeric(large_df['C'], downcast='integer')
- # 将字符串列转换为分类类型
- large_df['D'] = large_df['D'].astype('category')
- print("\n优化后内存使用:")
- print(large_df.memory_usage(deep=True))
- # 使用eval进行高效计算
- # 传统方法
- %timeit large_df['E'] = large_df['A'] + large_df['B'] * large_df['C']
- # 使用eval方法
- %timeit large_df.eval('F = A + B * C', inplace=True)
- # 使用query进行高效过滤
- # 传统方法
- %timeit filtered = large_df[(large_df['A'] > 0.5) & (large_df['C'] > 50)]
- # 使用query方法
- %timeit filtered_query = large_df.query('A > 0.5 and C > 50')
- # 使用itertuples进行高效行迭代
- # 传统iterrows方法
- %timeit for idx, row in large_df.head(1000).iterrows(): pass
- # 使用itertuples方法
- %timeit for row in large_df.head(1000).itertuples(): pass
- # 使用apply与向量化操作
- # 创建一个函数
- def custom_function(x):
- return x * 2 + 1
- # 使用apply
- %timeit result_apply = large_df['A'].apply(custom_function)
- # 使用向量化操作
- %timeit result_vectorized = custom_function(large_df['A'])
- # 使用cython或numba加速
- # 安装numba: pip install numba
- from numba import jit
- @jit(nopython=True)
- def numba_function(x):
- result = np.empty_like(x)
- for i in range(len(x)):
- result[i] = x[i] * 2 + 1
- return result
- %timeit result_numba = numba_function(large_df['A'].values)
复制代码
实战案例
数据分析实例
让我们通过一个实际的数据分析案例来综合运用前面学到的知识。假设我们有一份销售数据,需要进行清洗、分析和可视化。
- # 创建模拟销售数据
- np.random.seed(42)
- dates = pd.date_range('2022-01-01', '2022-12-31')
- products = ['A', 'B', 'C', 'D', 'E']
- regions = ['北部', '南部', '东部', '西部']
- # 生成随机数据
- n_records = 1000
- sales_data = {
- '日期': np.random.choice(dates, n_records),
- '产品': np.random.choice(products, n_records),
- '地区': np.random.choice(regions, n_records),
- '销售额': np.random.randint(100, 1000, n_records),
- '成本': np.random.randint(50, 500, n_records),
- '销售员': np.random.choice(['张三', '李四', '王五', '赵六', '钱七'], n_records)
- }
- df_sales = pd.DataFrame(sales_data)
- print("原始数据预览:")
- print(df_sales.head())
- # 数据清洗
- # 1. 检查缺失值
- print("\n缺失值统计:")
- print(df_sales.isna().sum())
- # 2. 添加利润列
- df_sales['利润'] = df_sales['销售额'] - df_sales['成本']
- # 3. 添加利润率列
- df_sales['利润率'] = df_sales['利润'] / df_sales['销售额']
- # 4. 添加月份列
- df_sales['月份'] = df_sales['日期'].dt.month
- # 5. 添加季度列
- df_sales['季度'] = df_sales['日期'].dt.quarter
- # 数据分析
- # 1. 按产品分析销售情况
- product_stats = df_sales.groupby('产品').agg({
- '销售额': ['sum', 'mean', 'count'],
- '利润': ['sum', 'mean'],
- '利润率': 'mean'
- })
- print("\n产品销售统计:")
- print(product_stats)
- # 2. 按地区分析销售情况
- region_stats = df_sales.groupby('地区').agg({
- '销售额': 'sum',
- '利润': 'sum'
- }).sort_values('销售额', ascending=False)
- print("\n地区销售统计:")
- print(region_stats)
- # 3. 按月份分析销售趋势
- monthly_sales = df_sales.groupby('月份')['销售额'].sum()
- print("\n月度销售趋势:")
- print(monthly_sales)
- # 4. 销售员业绩分析
- salesperson_stats = df_sales.groupby('销售员').agg({
- '销售额': 'sum',
- '利润': 'sum',
- '利润率': 'mean'
- }).sort_values('销售额', ascending=False)
- print("\n销售员业绩统计:")
- print(salesperson_stats)
- # 5. 产品-地区交叉分析
- product_region_pivot = pd.pivot_table(
- df_sales,
- values='销售额',
- index='产品',
- columns='地区',
- aggfunc='sum',
- margins=True
- )
- print("\n产品-地区交叉分析:")
- print(product_region_pivot)
- # 6. 找出最佳销售组合
- best_combinations = df_sales.groupby(['产品', '地区'])['销售额'].sum().sort_values(ascending=False).head(10)
- print("\n最佳销售组合:")
- print(best_combinations)
复制代码
数据可视化结合
让我们使用Matplotlib和Seaborn库来可视化我们的分析结果:
- import matplotlib.pyplot as plt
- import seaborn as sns
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
- plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
- # 1. 产品销售额对比
- plt.figure(figsize=(10, 6))
- product_sales = df_sales.groupby('产品')['销售额'].sum().sort_values(ascending=False)
- sns.barplot(x=product_sales.index, y=product_sales.values)
- plt.title('各产品销售额对比')
- plt.xlabel('产品')
- plt.ylabel('销售额')
- plt.show()
- # 2. 地区销售额占比
- plt.figure(figsize=(10, 6))
- region_sales = df_sales.groupby('地区')['销售额'].sum()
- plt.pie(region_sales, labels=region_sales.index, autopct='%1.1f%%', startangle=90)
- plt.title('各地区销售额占比')
- plt.axis('equal') # 使饼图呈正圆形
- plt.show()
- # 3. 月度销售趋势
- plt.figure(figsize=(12, 6))
- monthly_sales = df_sales.groupby('月份')['销售额'].sum()
- sns.lineplot(x=monthly_sales.index, y=monthly_sales.values, marker='o')
- plt.title('月度销售趋势')
- plt.xlabel('月份')
- plt.ylabel('销售额')
- plt.xticks(range(1, 13))
- plt.grid(True)
- plt.show()
- # 4. 销售员业绩对比
- plt.figure(figsize=(10, 6))
- salesperson_performance = df_sales.groupby('销售员')['销售额'].sum().sort_values(ascending=False)
- sns.barplot(x=salesperson_performance.index, y=salesperson_performance.values)
- plt.title('销售员业绩对比')
- plt.xlabel('销售员')
- plt.ylabel('销售额')
- plt.show()
- # 5. 产品-地区热力图
- plt.figure(figsize=(10, 8))
- product_region_heatmap = pd.pivot_table(
- df_sales,
- values='销售额',
- index='产品',
- columns='地区',
- aggfunc='sum'
- )
- sns.heatmap(product_region_heatmap, annot=True, fmt='.0f', cmap='YlGnBu')
- plt.title('产品-地区销售额热力图')
- plt.xlabel('地区')
- plt.ylabel('产品')
- plt.show()
- # 6. 销售额与利润散点图
- plt.figure(figsize=(10, 6))
- sns.scatterplot(x='销售额', y='利润', hue='产品', data=df_sales, alpha=0.7)
- plt.title('销售额与利润关系')
- plt.xlabel('销售额')
- plt.ylabel('利润')
- plt.show()
- # 7. 利润率分布
- plt.figure(figsize=(10, 6))
- sns.histplot(df_sales['利润率'], bins=20, kde=True)
- plt.title('利润率分布')
- plt.xlabel('利润率')
- plt.ylabel('频次')
- plt.show()
- # 8. 季度销售对比
- plt.figure(figsize=(10, 6))
- quarterly_sales = df_sales.groupby('季度')['销售额'].sum()
- sns.barplot(x=quarterly_sales.index, y=quarterly_sales.values)
- plt.title('季度销售对比')
- plt.xlabel('季度')
- plt.ylabel('销售额')
- plt.show()
复制代码
总结与最佳实践
通过本文的全面解析,我们深入了解了Pandas DataFrame数据结构的基础概念和高级技巧。下面总结一些关键点和最佳实践:
关键点总结
1. DataFrame基础:DataFrame是Pandas中最重要的数据结构,类似于Excel表格或SQL表,由行和列组成。
2. 数据创建:可以通过字典、列表、NumPy数组或从文件读取等多种方式创建DataFrame。
3. 数据选择:使用loc(基于标签)、iloc(基于位置)和条件过滤等方法选择数据。
4. 数据清洗:处理缺失值、重复值、异常值和数据类型转换是数据预处理的重要步骤。
5. 数据转换:添加/删除列、应用函数、分组操作和数据透视表是数据转换的常用方法。
6. 高级技巧:多级索引、时间序列处理、合并与连接和性能优化技巧可以提高数据分析的效率和灵活性。
DataFrame基础:DataFrame是Pandas中最重要的数据结构,类似于Excel表格或SQL表,由行和列组成。
数据创建:可以通过字典、列表、NumPy数组或从文件读取等多种方式创建DataFrame。
数据选择:使用loc(基于标签)、iloc(基于位置)和条件过滤等方法选择数据。
数据清洗:处理缺失值、重复值、异常值和数据类型转换是数据预处理的重要步骤。
数据转换:添加/删除列、应用函数、分组操作和数据透视表是数据转换的常用方法。
高级技巧:多级索引、时间序列处理、合并与连接和性能优化技巧可以提高数据分析的效率和灵活性。
最佳实践
1. 数据加载优化:对于大型文件,使用chunksize参数分块读取只加载需要的列,使用usecols参数指定适当的数据类型,减少内存使用
2. 对于大型文件,使用chunksize参数分块读取
3. 只加载需要的列,使用usecols参数
4. 指定适当的数据类型,减少内存使用
• 对于大型文件,使用chunksize参数分块读取
• 只加载需要的列,使用usecols参数
• 指定适当的数据类型,减少内存使用
- # 示例:优化大型CSV文件读取
- # 只加载需要的列,并指定数据类型
- dtypes = {'列1': 'int32', '列2': 'category', '列3': 'float32'}
- df = pd.read_csv('large_file.csv', usecols=['列1', '列2', '列3'], dtype=dtypes)
- # 分块读取大型文件
- chunk_size = 100000
- chunks = pd.read_csv('very_large_file.csv', chunksize=chunk_size)
- for chunk in chunks:
- # 处理每个数据块
- process(chunk)
复制代码
1. 内存优化:使用适当的数据类型(如int8代替int64)将字符串列转换为category类型(当唯一值较少时)使用downcast参数减少数值类型的内存占用
2. 使用适当的数据类型(如int8代替int64)
3. 将字符串列转换为category类型(当唯一值较少时)
4. 使用downcast参数减少数值类型的内存占用
• 使用适当的数据类型(如int8代替int64)
• 将字符串列转换为category类型(当唯一值较少时)
• 使用downcast参数减少数值类型的内存占用
- # 示例:内存优化
- def optimize_memory(df):
- # 优化整数类型
- int_cols = df.select_dtypes(include=['int64']).columns.tolist()
- df[int_cols] = df[int_cols].apply(pd.to_numeric, downcast='integer')
-
- # 优化浮点类型
- float_cols = df.select_dtypes(include=['float64']).columns.tolist()
- df[float_cols] = df[float_cols].apply(pd.to_numeric, downcast='float')
-
- # 转换对象类型为category(当唯一值少于50%时)
- obj_cols = df.select_dtypes(include=['object']).columns.tolist()
- for col in obj_cols:
- if df[col].nunique() / len(df[col]) < 0.5:
- df[col] = df[col].astype('category')
-
- return df
- df_optimized = optimize_memory(df.copy())
- print("优化前内存使用:", df.memory_usage(deep=True).sum() / 1024**2, "MB")
- print("优化后内存使用:", df_optimized.memory_usage(deep=True).sum() / 1024**2, "MB")
复制代码
1. 性能优化:使用向量化操作代替循环使用eval和query方法进行高效计算和过滤使用itertuples代替iterrows进行行迭代
2. 使用向量化操作代替循环
3. 使用eval和query方法进行高效计算和过滤
4. 使用itertuples代替iterrows进行行迭代
• 使用向量化操作代替循环
• 使用eval和query方法进行高效计算和过滤
• 使用itertuples代替iterrows进行行迭代
- # 示例:性能优化
- # 向量化操作代替循环
- # 不好的方式
- for i in range(len(df)):
- df.loc[i, '新列'] = df.loc[i, '列1'] * 2 + df.loc[i, '列2']
- # 好的方式
- df['新列'] = df['列1'] * 2 + df['列2']
- # 使用eval进行复杂计算
- df.eval('新列 = 列1 * 2 + 列2', inplace=True)
- # 使用query进行复杂过滤
- result = df.query('列1 > 10 and 列2 < 20')
- # 使用itertuples进行行迭代
- for row in df.itertuples():
- # 处理每一行
- process(row)
复制代码
1. 代码可读性与维护:使用有意义的列名和变量名将复杂操作分解为多个简单步骤添加注释解释关键步骤
2. 使用有意义的列名和变量名
3. 将复杂操作分解为多个简单步骤
4. 添加注释解释关键步骤
• 使用有意义的列名和变量名
• 将复杂操作分解为多个简单步骤
• 添加注释解释关键步骤
- # 示例:代码可读性与维护
- # 不好的方式
- df['c'] = df['a'] + df['b']
- d = df.groupby('c')['d'].mean().reset_index()
- # 好的方式
- # 计算两列之和
- df['sum_columns'] = df['column_a'] + df['column_b']
- # 按和分组并计算平均值
- average_by_sum = df.groupby('sum_columns')['value_column'].mean().reset_index()
- average_by_sum.rename(columns={'value_column': 'average_value'}, inplace=True)
复制代码
1. 链式操作:使用方法链式调用使代码更简洁适当时使用括号提高可读性
2. 使用方法链式调用使代码更简洁
3. 适当时使用括号提高可读性
• 使用方法链式调用使代码更简洁
• 适当时使用括号提高可读性
- # 示例:链式操作
- # 不好的方式
- df_filtered = df[df['value'] > 10]
- df_grouped = df_filtered.groupby('category')
- df_result = df_grouped.agg({'value': 'mean', 'count': 'sum'})
- # 好的方式
- df_result = (
- df[df['value'] > 10]
- .groupby('category')
- .agg({'value': 'mean', 'count': 'sum'})
- )
复制代码
1. 错误处理:使用try-except块处理可能的异常检查数据质量和完整性
2. 使用try-except块处理可能的异常
3. 检查数据质量和完整性
• 使用try-except块处理可能的异常
• 检查数据质量和完整性
- # 示例:错误处理
- def safe_divide(a, b):
- """安全除法,处理除零错误"""
- try:
- return a / b
- except ZeroDivisionError:
- return np.nan
- # 应用安全除法
- df['ratio'] = df.apply(lambda row: safe_divide(row['numerator'], row['denominator']), axis=1)
- # 检查数据质量
- def check_data_quality(df):
- """检查数据质量并报告问题"""
- issues = []
-
- # 检查缺失值
- missing = df.isna().sum()
- if missing.any():
- issues.append(f"缺失值: {missing[missing > 0].to_dict()}")
-
- # 检查重复行
- duplicates = df.duplicated().sum()
- if duplicates > 0:
- issues.append(f"重复行: {duplicates}")
-
- # 检查异常值(使用IQR方法)
- numeric_cols = df.select_dtypes(include=['number']).columns
- for col in numeric_cols:
- Q1 = df[col].quantile(0.25)
- Q3 = df[col].quantile(0.75)
- IQR = Q3 - Q1
- outliers = ((df[col] < (Q1 - 1.5 * IQR)) | (df[col] > (Q3 + 1.5 * IQR))).sum()
- if outliers > 0:
- issues.append(f"列 {col} 中发现 {outliers} 个异常值")
-
- return issues
- quality_issues = check_data_quality(df)
- if quality_issues:
- print("数据质量问题:")
- for issue in quality_issues:
- print(f"- {issue}")
- else:
- print("未发现明显的数据质量问题")
复制代码
通过遵循这些最佳实践,你可以更高效地使用Pandas DataFrame进行数据处理和分析,同时保持代码的可读性和可维护性。随着经验的积累,你将能够更加熟练地运用这些技巧,成为真正的数据处理高手。
版权声明
1、转载或引用本网站内容(全面解析Pandas DataFrame数据结构从基础概念到高级技巧助你成为数据处理高手)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://www.pixtech.org/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://www.pixtech.org/thread-31953-1-1.html
|
|