导语
Pandas除了基本的功能以外,还有一些非常重要的操作,如数据组合与分组,缺失值处理与apply自定义函数等,因此本博客来分享一下Pandas的数据组合分组操作,缺失值处理功能与apply调用函数的知识。
一.数据组合
1.concat函数连接DataFrame
使用concat函数将多个DataFrame连接起来,通过 iloc ,loc等方法取出连接后的数据的子集,代码如下:
import pandas as pd
# 创建两个 DataFrame
df1 = pd.DataFrame({'A': [1, 3, 4, ], 'B': [2, 4, 4]})
df2 = pd.DataFrame({'A': [5, 7, 4], 'B': [6, 8, 4]})
df3 = pd.DataFrame({'A': [5, 7, 6], 'B': [6, 8, 9]})
# 使用 concat 函数将两个 DataFrame 连接起来
concat_df = pd.concat([df1,df2,df3])
# 使用 iloc 方法取出连接后的数据的子集(基于位置索引)
subset_iloc = concat_df.iloc[1]
# 使用 loc 方法取出连接后的数据的子集(基于标签索引)
subset_loc = concat_df.loc[1]
print("连接后的 DataFrame:")
print(concat_df)
print()
print("基于位置索引的子集:")
print(subset_iloc)
print()
print("基于标签索引的子集:")
print(subset_loc)
连接后的 DataFrame:
A B
0 1 2
1 3 4
2 4 4
0 5 6
1 7 8
2 4 4
0 5 6
1 7 8
2 6 9
基于位置索引的子集:
A 3
B 4
Name: 1, dtype: int64
基于标签索引的子集:
A B
1 3 4
1 7 8
1 7 8
通过上述代码可以看到,通过concat函数可以将多个DataFrame连接到一起,而各个DataFrame的索引将会保留,如果不要保留原始的索引,可以在concat的时候,设置ignore_index,即:pd.concat([df1,df2,df3],ignore_index=True) 。
2.append函数添加数据行
concat可以连接多个对象,如果只需要向DataFrame追加一个对象,可以通过append函数来实现,使用Python字典添加数据行,代码演示如下:
import pandas as pd
df=pd.DataFrame({'a':[1,2],'b':[3,4]})
print('添加前df:',df)
print()
data_dict={'a':5,'b':6}
df1=df.append(data_dict,ignore_index=True)
print('添加后df:',df1)
添加前df: a b
0 1 3
1 2 4
添加后df: a b
0 1 3
1 2 4
2 5 6
concat还可以添加Series类型的数据,由于Series是列数据,concat方法默认是添加行,但是Series数据没有行索引,所以添加了一个新列,缺失的数据用NaN填充,NaN是Python用于表示“缺失值”的方法。代码如下所示:
newSeries=pd.Series([6,7])
pd.concat([df1,newSeries])
a b 0
0 1.0 3.0 NaN
1 2.0 4.0 NaN
2 5.0 6.0 NaN
0 NaN NaN 6.0
1 NaN NaN 7.0
3.merge()函数基于特定键合并数据
merge()函数与concat()函数都用于连接数据,不同的是,merge() 函数主要用于基于特定键合并数据,用法与sql很像,演示代码如下:
customer_data = pd.DataFrame({'customer_id': [1, 2, 3, 4],
'name': ['Alice', 'Bob', 'Charlie', 'David']})
order_data = pd.DataFrame({'order_id': [101, 102, 103, 104],
'customer_id': [2, 3, 2, 1],
'product': ['A', 'B', 'C', 'A']})
merged_data = pd.merge(customer_data,order_data,on='customer_id')
print('\ncustomer_data:')
print(customer_data)
print('\norder_data:')
print(order_data)
print('\nmerged_data:')
print(merged_data)
customer_data:
customer_id name
0 1 Alice
1 2 Bob
2 3 Charlie
3 4 David
order_data:
order_id customer_id product
0 101 2 A
1 102 3 B
2 103 2 C
3 104 1 A
merged_data:
customer_id name order_id product
0 1 Alice 104 A
1 2 Bob 101 A
2 2 Bob 103 C
3 3 Charlie 102 B
concat() 函数主要用于沿着轴方向拼接数据。如果需要基于特定键进行合并操作,那么使用 merge() 函数更为合适;如果只是简单的拼接数据而不需要基于键进行合并,那么使用 concat() 函数即可。merge() 函数主要用于类似数据库风格的合并,而 concat() 函数主要用于在维度上堆叠数据。
二.缺失数据处理
在实际业务中,我们处理数据时,经常会出现缺失值的情况,
Pandas中的NaN值来自NumPy库,NumPy中缺失值有几种表示形式:NaN,NAN,nan,他们都一样缺失值和其它类型的数据不同,它毫无意义,NaN不等于0,也不等于空串。而实际开发过程中,缺失值的来源主要来自于原始数据自带的和数据处理过程中产生的。
1.缺失值的查看
通常使用Missingno来对缺失值进行可视化,使用之前需要安装pip install missingno,然后导入模块import missingno as msno即可,使用msno.bar(数据)可以查看数据的缺失值情况。还可以用data.isnull().sum()来查看某列有多少个缺失值。
2.缺失值的删除
删除缺失值会损失信息,一般我们不删除,删除缺失值的方法是dropna(),如:
按行删除:
data.dropna(subset=['Age'],how='any',inplace=True)
按列删除:
当一列包含了很多缺失值的时候(比如超过80%),可以将该列删除,但最好不要删除数据
data.drop(['Age'],axis = 1).head()
3.缺失值的填充
缺失值的填充使用fillna()函数,如data.fillna(0,inplace=True),使用0填充数据中所有的缺失值。或者使用统计量进行替换(缺失值所处列的平均值,中位数,众数),如data['列名1'].fillna(data['列名1'].mean(),inplace=True),而对于时间序列的数据,可以设置fillna里的method参数,method='ffill'时,就是取NaN的前一个非空值进行填充,method='bfill'时,就是取NaN的后一个非空值进行填充。
除了向上取值和向下取值以外,还可以线性插值方法填充缺失值,data.interpolate(limit_direction='both')
除了上面介绍的填充缺失值的方法外,还可以使用机器学习算法预测来进行缺失值填充。
三.apply自定义函数
1.自定义函数
当Pandas里的api不能满足需求的时候,需要自己编写数据处理函数,这个时候可以使用apply函数,apply函数可以接收一个自定义函数,可以讲DataFrame的行/列数据传递给自定义函数处理,apply函数类似于编写一个for循环,遍历行/列的每一个元素,但是比使用for循环效率高很多。
import pandas as pd
df=pd.DataFrame({'a':[1,2,3],'b':[3,4,5]})
def my_sq(x):
return x**2
df.apply(my_sq)
a b
0 1 9
1 4 16
2 9 25
2.向量化函数
def定义的函数无法对向量进行处理,代码如下:
df = pd.DataFrame({'a':[10,20,30],'b':[20,30,40]})
def avg(x,y):
if(x==20):
return (np.NaN)
else:
return (x+y)/2
avg(df['a'],df['b'])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_27504/2984005764.py in <module>
6 else:
7 return (x+y)/2
----> 8 avg(df['a'],df['b'])
9
~\AppData\Local\Temp/ipykernel_27504/2984005764.py in avg(x, y)
2
3 def avg(x,y):
----> 4 if(x==20):
5 return (np.NaN)
6 else:
D:\Users\ChenZ\anaconda3\lib\site-packages\pandas\core\generic.py in __nonzero__(self)
1535 @final
1536 def __nonzero__(self):
-> 1537 raise ValueError(
1538 f"The truth value of a {type(self).__name__} is ambiguous. "
1539 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
可以看到,这里df['a']是向量,而函数里的20是标量,所以无法直接计算,这时就需要使用np.vectorize,来讲函数向量化,我们可以在def函数的时候,使用装饰器,来对函数进行向量化:
import pandas as pd
import numpy as np
df = pd.DataFrame({'a':[10,20,30],'b':[20,30,40]})
@np.vectorize
def avg(x,y):
if(x==20):
return (np.NaN)
else:
return (x+y)/2
avg(df['a'],df['b'])
array([15., nan, 35.])
可以看到,加了@np.vectorize装饰器以后,函数可以计算向量的内容了
3.lambda函数
当函数比较简单的时候,没有必要创建一个def函数,就可以使用lambda表达式创建匿名函数,代码如下:
print('df:')
print(df)
df.apply(lambda x:x+1)
df:
a b
0 10 20
1 20 30
2 30 40
a b
0 11 21
1 21 31
2 31 41
四.数据分组
在sql中,我们经常使用GROUP BY将某个字段,按不同的取值进行分组,在pandas中也有groupby函数,分组之后,每组都会有最少1条数据,将这些数据进一步处理,返回单个值的过程就是聚合,比如分组之后计算算数平均值,或者分组之后计算频数,都属于聚合
1.单变量分组聚合
# 这里我们读取一个文件
# 我们读取一个文件
df=pd.read_csv('gapminder.tsv',sep='\t')
df
country continent year lifeExp pop gdpPercap
0 Afghanistan Asia 1952 28.801 8425333 779.445314
1 Afghanistan Asia 1957 30.332 9240934 820.853030
2 Afghanistan Asia 1962 31.997 10267083 853.100710
3 Afghanistan Asia 1967 34.020 11537966 836.197138
4 Afghanistan Asia 1972 36.088 13079460 739.981106
... ... ... ... ... ... ...
1699 Zimbabwe Africa 1987 62.351 9216418 706.157306
1700 Zimbabwe Africa 1992 60.377 10704340 693.420786
1701 Zimbabwe Africa 1997 46.809 11404948 792.449960
1702 Zimbabwe Africa 2002 39.989 11926563 672.038623
1703 Zimbabwe Africa 2007 43.487 12311143 469.709298
1704 rows × 6 columns
上述数据是各个州每年的人口和预期年龄数据,gdpPercap的意思是国内人均生产总值,把数据根据year字段进行分组,统计每年预期年龄的平均值,可以先使用groupby对年份进行分组,然后取lifeExp计算平均数,代码如下:
df.groupby('year').lifeExp.mean()
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
当然上述代码也可以调用Numpy库的mean函数,效果都是一样的:
df.groupby('year').lifeExp.agg(np.mean)
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
也可以自定义函数,然后在agg中调用它,如:
def my_mean(values):
n=len(values)
sum1=0
for v in values:
sum1+=v
return sum1/n
df.groupby('year').lifeExp.agg(my_mean)
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
分组后如果想计算多个聚合函数,可以把它们全部放入一个python列表,然后把整个列表传入agg:
df.groupby('year').lifeExp.agg([np.mean,np.std,np.count_nonzero])
mean std count_nonzero
year
1952 49.057620 12.225956 142
1957 51.507401 12.231286 142
1962 53.609249 12.097245 142
1967 55.678290 11.718858 142
1972 57.647386 11.381953 142
1977 59.570157 11.227229 142
1982 61.533197 10.770618 142
1987 63.212613 10.556285 142
1992 64.160338 11.227380 142
1997 65.014676 11.559439 142
2002 65.694923 12.279823 142
2007 67.007423 12.073021 142
还可以向agg/aggregate中传入字典,分组之后,可以对多个字段用不同的聚合,聚合后的列名就是聚合函数的名字,可以通过rename进行重命名,代码如下:
df.groupby('year').agg({'lifeExp': 'mean', 'pop': 'median', 'gdpPercap': 'median'}).rename(columns={'lifeExp': '平均寿命', 'pop': '人口', 'gdpPercap': '人均Gdp'}).reset_index()
year 平均寿命 人口 人均Gdp
0 1952 49.057620 3943953.0 1968.528344
1 1957 51.507401 4282942.0 2173.220291
2 1962 53.609249 4686039.5 2335.439533
3 1967 55.678290 5170175.5 2678.334740
4 1972 57.647386 5877996.5 3339.129407
5 1977 59.570157 6404036.5 3798.609244
6 1982 61.533197 7007320.0 4216.228428
7 1987 63.212613 7774861.5 4280.300366
8 1992 64.160338 8688686.5 4386.085502
9 1997 65.014676 9735063.5 4781.825478
10 2002 65.694923 10372918.5 5319.804524
11 2007 67.007423 10517531.0 6124.371108
2.transform转换
transform需要把DataFrame中的值传递给一个函数,而后由该函数'转换'数据,agg(聚合)会返回单个聚合值,而transform不会减少数据量,如计算每个班级学生分数的平均值:
import pandas as pd
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva', 'Frank'],
'Class': ['A', 'B', 'A', 'B', 'A', 'B'],
'Score': [80, 75, 90, 85, 88, 92]
}
df = pd.DataFrame(data)
df
Name Class Score
0 Alice A 80
1 Bob B 75
2 Charlie A 90
3 David B 85
4 Eva A 88
5 Frank B 92
# 计算每个班级学生成绩的平均值
class_mean = df.groupby('Class')['Score'].transform('mean')
# 将计算得到的平均值填充到原始数据中的一个新列中
df['Class_Mean'] = class_mean
df
Name Class Score Class_Mean
0 Alice A 80 86.0
1 Bob B 75 84.0
2 Charlie A 90 86.0
3 David B 85 84.0
4 Eva A 88 86.0
5 Frank B 92 84.0
3.filtered过滤
使用groupby方法还可以过滤数据,调用filter方法,传入一个返回布尔值的函数,返回False的数据会被过滤掉,代码如下:
import pandas as pd
data = {
'A': [1, 2, 3, 4],
'B': [5, 6, 7, 8],
'C': [9, 10, 11, 12]
}
df = pd.DataFrame(data)
df
A B C
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
#使用filter函数
filtered_columns = df.filter(items=['A', 'C']) # 选择列标签为'A'和'C'的列
print(filtered_columns)
A C
0 1 9
1 2 10
2 3 11
3 4 12
4.grouped.groups分组
grouped是一个DataFrameGroupBy对象,可以通过groups属性查看计算过的分组,代码如下:
import pandas as pd
data = {
'Date': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03'],
'Category': ['A', 'B', 'A', 'B', 'A'],
'Sales': [100, 200, 150, 300, 180]
}
df = pd.DataFrame(data)
df
#输出结果
Date Category Sales
0 2023-01-01 A 100
1 2023-01-01 B 200
2 2023-01-02 A 150
3 2023-01-02 B 300
4 2023-01-03 A 180
grouped = df.groupby('Date')
groups = grouped.groups
for date, indices in groups.items():
print(f'日期为{date},其对应的行索引为{indices}')
#输出结果
日期为2023-01-01,其对应的行索引为Int64Index([0, 1], dtype='int64')
日期为2023-01-02,其对应的行索引为Int64Index([2, 3], dtype='int64')
日期为2023-01-03,其对应的行索引为Int64Index([4], dtype='int64')
由上述代码可以看出,df.groupby('Date').groups的对象,是一个字典,其键为日期,值为日期所在的行索引,通过groups对象可以查看计算过的分组
而通过get_group返回的对象可以看到该分组下的所有数据
代码如下:
import pandas as pd
data = {
'Date': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03'],
'Category': ['A', 'B', 'A', 'B', 'A'],
'Sales': [100, 200, 150, 300, 180]
}
df = pd.DataFrame(data)
df.groupby('Category').get_group('A')
#结果为:
Date Category Sales
0 2023-01-01 A 100
2 2023-01-02 A 150
4 2023-01-03 A 180
五.总结
本博客在上一篇博客的基础上,补充了很多实用的数据分析知识,但是目前这些还只是基础部分,下一篇将继续延伸Pandas的知识,再介绍一些高级技巧。