Pandas--数据组合与分组&&缺失值处理&&apply

导语 

        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填充,NaNPython用于表示“缺失值”的方法。代码如下所示:

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中缺失值有几种表示形式:NaNNANnan,他们都一样缺失值和其它类型的数据不同,它毫无意义,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的知识,再介绍一些高级技巧。

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值