【Pandas学习笔记Task02】:pandas基础


一、pandas数据结构

pandas的基本数据结构,包括一维的Series和二维的DataFrame。Series可以理解为一个字典,DataFrame就是Series的一个扩展。

1.Series

s = pd.Series(data = [100, 'a'], index= ['id1', 20], name='my')
# id1    100
# 20       a
# Name: my, dtype: object

# index是以Index对象存的,可以转为列表
print(list(s.index))    # ['id1', 20]

# 利用[index_item]取出单个索引对应的值.(这里是Series,DataFrame利用[index_item]取的是列索引)
print(s['id1'])     # 100

2.DataFrame

data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]]
# index代表行索引,column代表列索引
df = pd.DataFrame(data, index= ['row_%d'%i for i in range(3)], columns=['col_%d'%i for i in range(3)])
#        col_0 col_1  col_2
# row_0      1     a    1.2
# row_1      2     b    2.2
# row_2      3     c    3.2

# 这里data存的字典,每个value代表是一列。如果指定了column,需指定index否则会提示DataFrame为空,index为空。而data是字典形式已经给出了列名,后面若再给columns则所有值都是NaN。
df = pd.DataFrame(data = {'col_0': [1, 2, 3], 'col_1': ['a', 'b', 'c'], 'col_2': [1.2, 2.2, 3.2]},
                   columns = ['row_%d'%i for i in range(3)])
# Empty DataFrame
# Columns: [row_0, row_1, row_2]
# Index: []

3、对DataFrame行列的访问

print(df.values)	# 得到的是df里面的值组成列表
print(df['School']) # 以字典形式访问就是访问某列,其实得到的是Series.
print(df[['School', 'Grade']])  # 访问某些特定列,把要访问的列以列表传给df[],其实还是DataFrame.
print(df.School)    # 也可以直接将列名当做属性访问
print(df[6:7])   # 可以使用切片的形式访问第七行,若直接用7访问第7行会报错
print(df.iloc[1])   # iloc代表的是访问位置,使用iloc访问某一行(也可以访问某行某列)
print(df[df.columns[:7]])   # 访问df的前七列.先得到前七列索引值,然后再用df去访问

二、常用函数

1.汇总函数

head、tail函数分别表示可以取前后者后n行的结果,默认为5.

df = pd.read_csv('data/learn_pandas.csv')
df = df[df.columns[:7]]
# 取前两行
print(df.head(2))
#                           School     Grade  ... Weight Transfer
# 0  Shanghai Jiao Tong University  Freshman  ...   46.0        N
# 1              Peking University  Freshman  ...   70.0        N
#
# [2 rows x 7 columns]
# 取最后两行
print(df.tail(2))
#                             School      Grade  ... Weight Transfer
# 198  Shanghai Jiao Tong University     Senior  ...   71.0        N
# 199            Tsinghua University  Sophomore  ...   51.0        N
#
# [2 rows x 7 columns]

#info得到表的基本信息,describe得到表中数值列对应的一些统计量
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 200 entries, 0 to 199
# Data columns (total 7 columns):
#  #   Column    Non-Null Count  Dtype
# ---  ------    --------------  -----
#  0   School    200 non-null    object
#  1   Grade     200 non-null    object
#  2   Name      200 non-null    object
#  3   Gender    200 non-null    object
#  4   Height    183 non-null    float64
#  5   Weight    189 non-null    float64
#  6   Transfer  188 non-null    object
# dtypes: float64(2), object(5)
# memory usage: 11.1+ KB
df.describe()
#            Height      Weight
# count  183.000000  189.000000
# mean   163.218033   55.015873
# std      8.608879   12.824294
# min    145.400000   34.000000
# 25%    157.150000   46.000000
# 50%    161.900000   51.000000
# 75%    167.500000   65.000000
# max    193.900000   89.000000
#

2.特征统计函数

在 Series 和 DataFrame 上定义了许多统计函数,最常见的是 sum, mean, median, var, std, max, min 。这些统计函数与之前学习numpy时的大同小异。

df_demo = df[['Height', 'Weight']]
print(df_demo.mean())   # 每一列的平均值
print(df_demo.quantile(0.75))   # 列的分位数
print(df_demo.count())  # 非缺失值个数
# 函数返回标量,又称为聚合函数。有axis参数代表维度。0代表按列聚合(默认),1代表按行聚合。联想到numpy中的一些统计函数如sum()

3.唯一值函数

# 唯一值函数:unique得到唯一值组成的列表,nunique得到唯一值的个数.
print(df['School'].unique())  # 只支持1列,否则报错,因为DataFrame没有该属性
print(df['School'].nunique())   # 4 DataFrame有,但其实也是按多个Series来计算的,如下
print(df[['School','Grade']].nunique())   
# School    4
# Grade     4
# dtype: int64

print(df['School'].value_counts())  # 得到唯一值以及其有多少个值

# 多个列组合的唯一值要用drop_duplicates(列名),keep的值last代表保留最后出现该组合的行
# False和first、last有区别
# keep等于False,表示若该组合有值重复出现,那这组值所在的所有行都删掉(EX1第2问体会到)
print(df.drop_duplicates(['School', 'Grade'], keep='last'))

4.替换函数

一般来说替换针对Series,也就是某一列。
替换包括三类:映射替换、逻辑替换、数值替换。
其中映射替换可以理解为将x→y;
逻辑替换可以理解为满足/(不满足)条件则执行某种替换;

# 映射替换,可以通过字典,也可以通过列表实现对应位置的替换
# 这里将Male替换为0;Female替换为1
print(df['Gender'].replace({'Male': 0, 'Female': 1}).head())
print(df['Gender'].replace(['Female', 'Male'], [0, 1]).head())

# 逻辑替换 where(和numpy的where像), mask
s = pd.Series([-1, 1.123, 1000, 21])
print(s.where(s < 0, 100))    # 满足括号里面条件的不换
print(s.mask(s > 10, 20)) # 满足括号条件的换
bo = [True, False, False, False]
print(s.where(bo, 20))    # 传入一个布尔值的列表来表示谁换,换成什么。

# 数值替换 round, abs, clip方法
s = pd.Series([-1, 1.123, 1000, 221])
print(s.round(2))    # 取整,参数代表n位
print(s.abs())  # 绝对值
print(s.clip(1,100)) # 截断,前两个数字表示上下截断边界

5.排序函数

排序分为按照值排序(sort_values) 和按照索引排序(sort_index)。

# 值排序
print(df.sort_values('Height').head())    # 按height的值排序

# 对多个列排序,会先排sort_values指定排序列表前面的,若相同,则排后面的,默认升序,可以在ascending中指定升序(True)降序(False)
print(df.sort_values(['Weight', 'Height'], ascending=[True, False]).head())

# 索引排序,按照要排序的索引的值对表进行排序
df = df.set_index(['Weight', 'Height'])	# 做个测试,首先将weight和Height设为索引
print(df.sort_index(level=['Weight', 'Height'], ascending=[False, False]).head())

练习

Ex1:口袋妖怪数据集

.现有一份口袋妖怪的数据集,下面进行一些背景说明:

  • # 代表全国图鉴编号,不同行存在相同数字则表示为该妖怪的不同状态
  • 妖怪具有单属性和双属性两种,对于单属性的妖怪, Type 2 为缺失值
  • Total, HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 分别代表种族值、体力、物攻、防御、特攻、特防、速度,其中种族值为后6项之和
  1. HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 进行加总,验证是否为 Total 值。

  2. 对于 # 重复的妖怪只保留第一条记录,解决以下问题:

    求第一属性的种类数量和前三多数量对应的种类
    求第一属性和第二属性的组合种类
    求尚未出现过的属性组合

  3. 按照下述要求,构造 Series :
    取出物攻,超过120的替换为 high ,不足50的替换为 low ,否则设为 mid
    取出第一属性,分别用 replace 和 apply 替换所有字母为大写 求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df 并从大到小排序

df = pd.read_csv('data/pokemon.csv')
print(df.head(3))

1、对 HP, Attack, Defense, Sp. Atk, Sp. Def, Speed 进行加总,验证是否为 Total 值。

df_demo1 = df[df.columns[5:]] # 先得到这些属性组成的df
# print(df_demo1.sum(1).head())    # 求行的和,这不就是逐行聚合嘛
if(list(df_demo1.sum(1)) == list(df['Total'])):
    print('True')
else:
    print('False')
    
# 参考答案
(df[['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']].sum(1)!=df['Total']).mean()

通过列索引获取属性的值然后逐行求和,将值与total进行比较即可。但是这里如果不利用list转为列表的话不能直接用==比较,会报错ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().。因为原本都是属于Series,我的理解是值类型不明确,所以转为list后可以进行比较。后来大佬解释了,为什么用if不行:

if里面只能传一个布尔值,序列之间判断之后返回的是bool的Series,里面有不同的bool,if不知道根据哪一个判断,如果一定要用的话就改成if(df_demo1.sum(1)!=df.Total).all()

(这些细节让我备受打击,也让我感受到要学习的还有太多太多…too much…)
2、

df_demo2 = df.drop_duplicates(['#'], keep='first')
print(df_demo2)

# a 种类数量
tp1_type_num = df_demo2['Type 1'].nunique()
print(tp1_type_num) # 18

# a 前三多数量对应的种类
top3_tp1_num = df_demo2['Type 1'].value_counts()[:3]
print(top3_tp1_num) 
# # Water     105
# # Normal     93
# # Grass      66
# Name: Type 1, dtype: int64

# b 求第一属性和第二属性的组合种类
# 先只得到这两列组成的表,不指出列的话,默认按全部列来算
p_type = df_demo2[['Type 1', 'Type 2']].drop_duplicates(keep='first')  
# # [143 rows x 2 columns]

# c 求尚未出现过的属性组合(解答错误)
tp2_type_num = df_demo2['Type 2'].nunique()	# 也是18,去掉了nan

# 从Type1和Type2的(不重复)种类中分别选值然后组合,判断该组合是否在出现过的组合种类集合里
tp1_type = df_demo2['Type 1'].unique()
tp2_type = df_demo2['Type 2'].unique()	# 这里有nan值,不知如何处理了 (19),但是nunique是18
unappear_type = [[tp1, tp2] for tp1 in tp1_type 
							for tp2 in tp2_type 
							if (tp1, tp2) not in tp_type.values.tolist()] # 把nan算作一个值

第2问中,a、b问题可以直接通过相关函数进行提取。但是在c问题上我的上述解法存在问题。
我的思路是:从Type1和Type2的(不重复)种类中分别选值然后组合,判断该组合是否在出现过的组合种类集合里,若不在那就是未出现过的属性组合。
想法过于美好(未考虑复杂度问题),在实现过程中的最后一步出岔子了。已经实现找到Type1Type2的种类,通过b问题也得到了表中出现的组合种类,然后打算通过两个遍历拿到(Type1,Type2)值的数对,然后利用not in来判断是否存在已出现的组合种类中。问题就出现在这里,not in无法实现判断是否在列表里面,可能因为这是一个二维的吧。
于是做了一个测试:

l1 = [[1, 2], [2, 3]]
print([[i, j] for i in range(3) for j in range(3) if (i, j) in l1])
# []

看来有些基础还未理解透彻。这里不能直接用not in来判断了。

3、

df_demo3 = df['Attack']
# 不能将元素取完放回再取,共三次比,而是将该元素连续比三次得到结果('>' not supported between instances of 'str' and 'int' )
# 不能使用and,逻辑运算符,只能用&执行位运算,因为df_demo3是一个Series形式而不是数值去参与比较
df_demo3 = df_demo3.mask(df_demo3 > 120, 'high').mask(df_demo3 < 50, 'low').mask((df_demo3 >= 50) & (df_demo3 <= 120), 'mid')
print(df_demo3)

第三题一开始我用三条语句进行分别比较,但是发现自己太天真。因为替换前是整型替换后就是字符了,执行比较时就报错(’>’ not supported between instances of ‘str’ and ‘int’)看了参考答案才发现原来可以直接连用三个逻辑替换,于是不经意中又采用and进行连接两个条件的,但是会报错。因为不能使用and逻辑运算符,只能用&执行位运算,因为df_demo3是一个Series形式而不是数值去参与比较,而且两个括号也要括起来。(我太难了 too sad.)

b 取出第一属性,分别用 replace 和 apply 替换所有字母为大写

# replace
df_demo4 = df['Type 1'].replace(tp1_type, [st.upper() for st in tp1_type])
print(df_demo4)

# apply
df_demo5 = df['Type 1'].apply(lambda x: x.upper())
print(df_demo5)

这里replace方法就是按照其参数模板,往里面传要替换的值。而且字符串有内置的转字母为大写的函数;在用apply方法之前觉得挺难,写起来发现对于这道题…很快啊,传一个变大写的函数就搞定,而且这个还是内置的方法,再看看答案,interesting.

求每个妖怪六项能力的离差,即所有能力中偏离中位数最大的值,添加到 df 并从大到小排序

df_demo6 = df[df.columns[5:]]
df['deviation'] = df_demo6.apply(lambda x: np.max((x - x.median()).abs()), 1)
print(df.sort_values('deviation', ascending=False).head())

这里apply的运用过程稍微复杂一些,直接上答案了。看了才知道,哦原来是,按行取元素然后减去中位数取绝对值并找到最大的值,这些我都能想到,但就是实现起来…
这道题本来还疑惑如何给DataFrame添加列,原来直接给一个索引值就行。(想想确实没什么啊,没把字典联系起来,哎,看来思考这道题时思维太局限了,发散思维有待加强)

总结

通过学习,感觉虽然很多方法看着很简单,但是因为自己还不太熟练的缘故,具体应用起来就有些困难。重要的在于思维,而且要将解题思维正确表达出来,还有很长的路要走。这一部分有很多的东西也还没有完全厘清,还要继续进一步学习。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值