pandas task-08 组队学习
import numpy as np
import pandas as pd
str对象
str对象的设计意图
str
对象为定义在Index
或Series
上的属性,专门用于逐元素处理文本内容,其内部定义了大量方法, pandas
沿用了Python
的标准库中str
模块中的部分函数以保证使用上的便利
var = 'abcd'
print(str.upper(var))
print('-'*50)
s = pd.Series(['abcd','efg','hi'])
print(s.str)
print(s.str.upper())
ABCD
--------------------------------------------------
<pandas.core.strings.StringMethods object at 0x00000251988430B8>
0 ABCD
1 EFG
2 HI
dtype: object
[]索引器
对于str
对象而言,[]
索引器对字符串进行了序列化的操作, 可直接通过[]
取出某个位置的元素,也可通过切片获得字串,通过对str
对象使用[]
索引器,可以完成完全一致的功能,如果超出范围则返回缺失值.
var[0]
'a'
var[-1:0:-2]
'db'
s.str[0] ##实现功能类似于取全部字符串的首位字符
0 a
1 e
2 h
dtype: object
s.str[-1:0:-2]
0 db
1 g
2 i
dtype: object
s.str[2]
0 c
1 g
2 NaN
dtype: object
string类型
整体上,绝大部分对于object
和string
类型的序列使用str
对象方法产生的结果是一致的,但也存在两点较大差异.
-
尽量保证每一个序列中的值都是字符串的情况下才可以使用
str
属性,但并不是必须的,其必要条件是序列中至少有一个可迭代对象,包括但不限于字符串,字典,列表.对于一个迭代对象,string
类型的str
对象和object
类型的str
对象返回结果可能是不同的. -
string
类型是Nullable类型,而object
则不是.这意味着strin
类型的序列,若调用的str
方法返回值为整数Series
和布尔Series
是,其对应的dtype
是Int
和boolean
的Nullable
类型,但Object
类型则会返回int/float
和bool/object
,这取决于缺失值的存在与否.同时字符串的比较操作也具有相似的特性,string
返回Nullable
类型,但object
则不会 -
对于全体元素为数值类型的序列,即使其类型为
object
或者category
也不允许直接使用str
属性.若需要把数字当作string
类型处理,可通过astype
强制转换为string
类型的Series
s = pd.Series([{1:'temp_1', 2:'temp_2'},['a','b'],0.5,'my_string'])
s.str[1]
0 temp_1
1 b
2 NaN
3 y
dtype: object
'''
结果不同原因:
当序列类型为Object时,是对每一个元素进行[]索引,
遇到字典则返回相应键值,遇到列表则返回对应位置元素,
遇到不可迭代兑现则会返回缺失值, 遇到字符串则返回索引
String类型的str对象首先将整个元素转为字面意义上的字符串,
对于列表而言,第一个元素即为`[`,字典而言,则第一个元素为`{`
'''
s.astype('string').str[0]
0 {
1 [
2 0
3 m
dtype: string
s.astype('string').str[1]
0 1
1 '
2 .
3 y
dtype: string
pd.Series([{1:'temp_1'},[0,1],(0,1)]).astype('string').str[0]
0 {
1 [
2 (
dtype: string
s = pd.Series(['a'])
s.str.len()
0 1
dtype: int64
s.astype('string').str.len()
0 1
dtype: Int64
s = pd.Series(['a',np.nan])
s.str.len()
0 1.0
1 NaN
dtype: float64
s.astype('string').str.len()
0 1
1 <NA>
dtype: Int64
s == 'a'
0 True
1 False
dtype: bool
s.astype('string') == 'a'
0 True
1 <NA>
dtype: boolean
s = pd.Series([12, 345, 6789])
s.astype('string').str[1]
0 2
1 4
2 7
dtype: string
正则表达式基础
一般字符的匹配
正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具,对于一般的字符而言,它可以找到其所在的位置,python
中的re
模块的findall
函数可以匹配所有出现过但不重叠的模式
import re
re.findall('Apple', 'Apple! This Is an Apple')
['Apple', 'Apple']
元字符基础
元字符 | 描述 |
---|---|
. | 匹配除换行符之外的任意字符 |
[] | 字符类,匹配方括号中包含的任意字符 |
[^] | 否定字符类,匹配方括号中不包含的任意字符 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n,m} | 花括号,匹配前面字符至少n次,但是不超过m次 |
(xyz) | 字符组,按照确切的顺序匹配字符xyz |
分支结构,匹配符号之前的字符或后面的字符 | |
|转义符,它可以还原元字符原来的含义 | |
^ | 匹配行的开始 |
$ | 匹配行的结束 |
print(re.findall('.', 'abc'),'\n')
print(re.findall('[ac]','abc'),'\n')
print(re.findall('[^ac]', 'abc'),'\n')
print(re.findall('[ab]{2}', 'aaaabbbb'),'\n')
print(re.findall('aaa|bbb', 'aaaabbbb'),'\n')
print(re.findall('a\\?|a\*', 'aa?a*a'),'\n')
print(re.findall('a?.', 'abaacadaae'), '\n')
['a', 'b', 'c']
['a', 'c']
['b']
['aa', 'aa', 'bb', 'bb']
['aaa', 'bbb']
['a?', 'a*']
['ab', 'aa', 'c', 'ad', 'aa', 'e']
print(re.findall(r'a\?|a\*', r'aa?a*a'),'\n') #匹配 a?
print(re.findall(r'a\\?|a\*', r'aa?a*a'),'\n')#匹配 a? \\也为转移字符
print(re.findall(r'a\*|a\\?', r'a\aa?a*a'),'\n') # 匹配a\?
['a?', 'a*']
['a', 'a', 'a', 'a']
['a\\', 'a', 'a', 'a*', 'a']
简写字符集
简写 | 描述 |
---|---|
\w | 匹配所有字母、数字和下划线:[a-zA-Z0-9_ ] |
\W | 匹配所有非字母和数字的字符:[^\W] |
\d | 匹配数字:[0-9] |
\D | 匹配非数字:[^\d] |
\s | 匹配空格符:[\t\n\f\r\p{Z}] |
\S | 匹配非空格符:[^\s] |
\B | 匹配一组非空字符开头或结尾的位置,不代表具体字符 |
print(re.findall(r'.s', 'Apple! This Is an Apple'),'\n')
print(re.findall(r'\w{2}', '09 8? 7w c_ 9q p@'), '\n')
print(re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@'), '\n')
print(re.findall(r'.\s.', 'Constant dropping wears the stone.'),'\n')
print(re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+)号',
'上海市黄浦区方萍中路249号 上海市宝山区密山路5号'),'\n')
['is', 'Is']
['09', '7w', 'c_', '9q']
['8?', 'p@']
['t d', 'g w', 's t', 'e s']
[('黄浦区', '方萍中路', '249'), ('宝山区', '密山路', '5')]
文本处理的五类操作
拆分
- 使用
str.split
将字符串的列进行拆分,该方法第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数n
,是否展开为多个列expand
等参数. str.rsplit
函数方法同str.split
区别在于使用n
参数时为从右到左限制最大拆分次数.
**注意:**当前版本下str.rsplit
因bug
无法正常使用正则表达式进行分割
# s = pd.Series(['上海市黄浦区方萍中路249号','上海市宝山区密山路5号'])
s = pd.Series(['上海市黄浦区方浜中路249号',
'上海市宝山区密山路5号'])
print(s.str.split('[市区路]'),'\n')
#定义最大拆分次数为2, 当拆分次数超过2此后,后续不在继续拆分
print(s.str.split('[市区路]', n = 2, expand = True), '\n')
0 [上海, 黄浦, 方浜中, 249号]
1 [上海, 宝山, 密山, 5号]
dtype: object
0 1 2
0 上海 黄浦 方浜中路249号
1 上海 宝山 密山路5号
s.str.rsplit('[市区路]',n = 2, expand = True)
0 | |
---|---|
0 | 上海市黄浦区方浜中路249号 |
1 | 上海市宝山区密山路5号 |
合并
str.join
:用某个连接符把Series
中的字符串列表链接起来,若列表中出现了非字符串元素则返回缺失值.str.cat
:用于合并两个序列,主要参数为连接符sep
,连接形式join
以及缺失值替代符号na_rep
,连接形式默认为以索引为键的左连接.
s = pd.Series([['a','b'],[1,'a'],[['a','b'],'c']])
s.str.join('-')
0 a-b
1 NaN
2 NaN
dtype: object
s1 = pd.Series(['a','b'])
s2 = pd.Series(['cat', 'dog'])
s1.str.cat(s2, sep='-')
0 a-cat
1 b-dog
dtype: object
s2.index = [1,2]
s1.str.cat(s2, sep='-', na_rep = '?', join = 'outer')
0 a-?
1 b-cat
2 ?-dog
dtype: object
匹配
str.contains
:返回每个字符串是否包含正则模式的布尔序列str.startswith
及str.endswith
:返回每个字符串以给定模式为开始和结束的布尔序列,但都不支持正则表达式str.match
:通过正则表达式来检测开始和结束字符串的模式,返回值为每个字符串起始处是否符合给定正则模式的布尔序列str.find
和str.rfind
:分别返回从左到右和从右到左第一次匹配的位置的索引,未找到是则返回-1.这两个函数均不支持正则匹配,只能使用字符字串的匹配
s = pd.Series(['my_cat','he is fat','railway station'])
s.str.contains('\s\wat')
0 False
1 True
2 False
dtype: bool
s.str.startswith('my') #使用字符字串匹配
0 True
1 False
2 False
dtype: bool
s.str.endswith('t')
0 True
1 True
2 False
dtype: bool
s.str.match('m|h') #使用正则表达式匹配
0 True
1 True
2 False
dtype: bool
s.str.contains('^[m|h]')
0 True
1 True
2 False
dtype: bool
s.str.contains('[f|g]at|n$')
0 False
1 True
2 True
dtype: bool
s = pd.Series(['This is an apple. That is not an apple.'])
s.str.find('apple') #从左到右匹配,返回首字母索引
0 11
dtype: int64
s.str.rfind('apple') # 从右到左匹配,返回首字母的索引
0 33
dtype: int64
替换
注意: str.replace
不同于replace
函数,使用字符串替换时应当使用前者.
s = pd.Series(['a_1_b','c_?'])
s.str.replace('\d|\?', 'new', regex = True)
0 a_new_b
1 c_new
dtype: object
#需要对不同的部分进行有差别的替换时,可通过自组的方法,
#此时可通过传入自定义的替换函数来分别进行处理, 需要注意到
# group(k)代表匹配到的第k个子组
s = pd.Series(['上海市黄浦区方萍中路249号',
'上海市宝山区密山路5号',
'北京市昌平区北农路2号'])
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
district = {'昌平区':'CP District',
'黄浦区':'HP District',
'宝山区':'BS District'}
city = {'上海市':'Shanghai','北京市':'Beijing'}
road = {'方萍中路':'Mid Fangping Road',
'密山路':'Mishan Road',
'北农路':'Beinong Road'}
def my_func(m):
str_city = city[m.group(1)]
str_district = district[m.group(2)]
str_road = road[m.group(3)]
str_no = 'No.' + m.group(4)[:-1]
return ' '.join([str_city,str_district,str_road,str_no])
s.str.replace(pat, my_func, regex = True)
0 Shanghai HP District Mid Fangping Road No.249
1 Shanghai BS District Mishan Road No.5
2 Beijing CP District Beinong Road No.2
dtype: object
#表现子组代表的含义更加详细的表示
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
def my_func(m):
str_city = city[m.group('市名')]
str_district = district[m.group('区名')]
str_road = road[m.group('路名')]
str_no = 'No.'+m.group('编号')[:-1]
return ' '.join([str_city,str_district,str_road,str_no])
s.str.replace(pat, my_func, regex = True)
0 Shanghai HP District Mid Fangping Road No.249
1 Shanghai BS District Mishan Road No.5
2 Beijing CP District Beinong Road No.2
dtype: object
提取
str.extract
提取方法:返回一种具体元素(不是布尔值或元素对应的索引值)的匹配操作,也可认为是一种特殊的拆分操作str.extraclall
:不同于str.extract
只匹配一次,会将所有符合条件的模式全部匹配出来,若存在多个结果则以多级索引的方法存储str.findall
:功能类似于str.extractall
,区别为前者将结果存入列表中,后者则处理为多级索引,每一行只对应一组匹配,并未把所有的匹配组合构成列表
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
s.str.extract(pat)
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 上海市 | 黄浦区 | 方萍中路 | 249号 |
1 | 上海市 | 宝山区 | 密山路 | 5号 |
2 | 北京市 | 昌平区 | 北农路 | 2号 |
#也可通过对子组的命名直接对新生成的DataFrame的列命名
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
s.str.extract(pat)
市名 | 区名 | 路名 | 编号 | |
---|---|---|---|---|
0 | 上海市 | 黄浦区 | 方萍中路 | 249号 |
1 | 上海市 | 宝山区 | 密山路 | 5号 |
2 | 北京市 | 昌平区 | 北农路 | 2号 |
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'],index = ['my_A', 'my_B'])
pat = '[A|B](\d+)[T|S](\d+)'
s.str.extractall(pat) #多次匹配,将所有符合条件的模式全部匹配出来
0 | 1 | ||
---|---|---|---|
match | |||
my_A | 0 | 135 | 15 |
1 | 26 | 5 | |
my_B | 0 | 674 | 2 |
1 | 25 | 6 |
pat_with_name = '[A|B](?P<name1>\d+[T|S](?P<name2>\d+))'
s.str.extractall(pat_with_name)
name1 | name2 | ||
---|---|---|---|
match | |||
my_A | 0 | 135T15 | 15 |
1 | 26S5 | 5 | |
my_B | 0 | 674S2 | 2 |
1 | 25T6 | 6 |
s.str.findall(pat)
my_A [(135, 15), (26, 5)]
my_B [(674, 2), (25, 6)]
dtype: object
常用字符串函数
字母型函数
upper
:将字母转为大写lower
:将字母转为小写title
:将单词首字母转为大写capitalize
:将句子首字母转为大写swapcase
:将大写字母反转为小写字母, 小写字母反转为大写字母
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
s.str.upper()
0 LOWER
1 CAPITALS
2 THIS IS A SENTENCE
3 SWAPCASE
dtype: object
s.str.lower()
0 lower
1 capitals
2 this is a sentence
3 swapcase
dtype: object
s.str.title()
0 Lower
1 Capitals
2 This Is A Sentence
3 Swapcase
dtype: object
s.str.capitalize()
0 Lower
1 Capitals
2 This is a sentence
3 Swapcase
dtype: object
s.str.swapcase()
0 LOWER
1 capitals
2 THIS IS A SENTENCE
3 sWaPcAsE
dtype: object
数值型函数
pd.to_numeric
方法可以对字符格式的数值进行快速转换和筛选,其主要参数包括errors
和downcast
分别代表了非数值的处理模式和转换类型.不能转换为数值的有三种errors
选项,raise
,coerce
,ignore
分别表示报错,设为缺失以及保持原来的字符串.
技巧: 数据清洗时通过coerce
的设定快速查看非数值的设定
s = pd.Series(['1','2.2','2e','??','-2.1','0'])
pd.to_numeric(s, errors = 'ignore')
0 1
1 2.2
2 2e
3 ??
4 -2.1
5 0
dtype: object
pd.to_numeric(s, errors = 'coerce')
0 1.0
1 2.2
2 NaN
3 NaN
4 -2.1
5 0.0
dtype: float64
s[pd.to_numeric(s, errors = 'coerce').isna()]
2 2e
3 ??
dtype: object
统计型函数
count
:返回出现正则模式的次数
len
: 返回出现字符串的长度
s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
s.str.count(r'[r|f]at|ee')
0 2
1 2
dtype: int64
s.str.len()
0 14
1 19
dtype: int64
格式型函数
格式型函数主要分为两类,一种是除空型,一种是填充型.
- 除空型一共有三种,分别是
strip
,strip
,lstrip
,分别代表去除两侧空格,右侧空格和左侧空格. - 填充型函数中
pad
是最灵活的,可以选定字符串长度,填充内容和填充方向,填充的方向有left
,right
,both
三种. 同时也有其他的填充函数可以较为复杂的实现填充,分别是
rjust
:右侧为原始元素,填充左侧;
ljust
:左侧为原始元素,填充右侧;
center
:中间为原始元素,填充两侧.
#注意 第一个元素前面有空格 第二个元素后面有空格, 第三个元素前后均有空格
my_index = pd.Index([' col1', 'col2 ', ' col3 '])
my_index.str.strip().str.len()
Int64Index([4, 4, 4], dtype='int64')
my_index.str.rstrip().str.len()
Int64Index([5, 4, 5], dtype='int64')
my_index.str.lstrip().str.len()
Int64Index([4, 5, 5], dtype='int64')
#填充型函数
s = pd.Series(['a','b','c'])
s.str.pad(5, 'left','*')
0 ****a
1 ****b
2 ****c
dtype: object
s.str.pad(5, 'right', '*')
0 a****
1 b****
2 c****
dtype: object
s.str.pad(5, 'both', '*')
0 **a**
1 **b**
2 **c**
dtype: object
s.str.rjust(5, '*')
0 ****a
1 ****b
2 ****c
dtype: object
s.str.ljust(5,'*')
0 a****
1 b****
2 c****
dtype: object
s.str.center(5, '*')
0 **a**
1 **b**
2 **c**
dtype: object
# 数值补零的技巧使用
s = pd.Series([7, 155, 303000]).astype('string')
s.str.pad(6, 'left', '0')
0 000007
1 000155
2 303000
dtype: string
s.str.rjust(6, '0')
0 000007
1 000155
2 303000
dtype: string
s.str.zfill(6)
0 000007
1 000155
2 303000
dtype: string
df = pd.read_excel(r'C:\Users\yongx\Desktop\data\house_info.xls', usecols = ['floor','year','area','price'])
df.head()
floor | year | area | price | |
---|---|---|---|---|
0 | 高层(共6层) | 1986年建 | 58.23㎡ | 155万 |
1 | 中层(共20层) | 2020年建 | 88㎡ | 155万 |
2 | 低层(共28层) | 2010年建 | 89.33㎡ | 365万 |
3 | 低层(共20层) | 2014年建 | 82㎡ | 308万 |
4 | 高层(共1层) | 2015年建 | 98㎡ | 117万 |
df.year = pd.to_numeric(df['year'].str[:-2]).astype('Int64')
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 31568 entries, 0 to 31567
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 floor 31219 non-null object
1 year 12850 non-null Int64
2 area 31568 non-null object
3 price 31568 non-null object
dtypes: Int64(1), object(3)
memory usage: 1017.5+ KB
pat = '(\w层)(共(\d+)层)'
new_cols = df.floor.str.extract(pat).rename(columns = {0:'Level', 1:'Highest'})
df = pd.concat([df.drop(columns = 'floor'), new_cols], 1)
df.head(3)
year | area | price | Level | Highest | |
---|---|---|---|---|---|
0 | 1986 | 58.23㎡ | 155万 | 高层 | 6 |
1 | 2020 | 88㎡ | 155万 | 中层 | 20 |
2 | 2010 | 89.33㎡ | 365万 | 低层 | 28 |
s_area = pd.to_numeric(df.area.str[:-1])
s_price = pd.to_numeric(df.price.str[:-1])
df['avg_price'] = ((s_price / s_area) * 1e4).astype('int').astype('string') + '元/平米'
df.head(3)
year | area | price | Level | Highest | avg_price | |
---|---|---|---|---|---|---|
0 | 1986 | 58.23㎡ | 155万 | 高层 | 6 | 26618元/平米 |
1 | 2020 | 88㎡ | 155万 | 中层 | 20 | 17613元/平米 |
2 | 2010 | 89.33㎡ | 365万 | 低层 | 28 | 40859元/平米 |
df = pd.read_csv(r'C:\Users\yongx\Desktop\data\script.csv')
df.head(3)
Release Date | Season | Episode | Episode Title | Name | Sentence | |
---|---|---|---|---|---|---|
0 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | waymar royce | What do you expect? They're savages. One lot s... |
1 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | will | I've never seen wildlings do a thing like this... |
2 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | waymar royce | How close did you get? |
df.columns = df.columns.str.strip()
df.head(3)
Release Date | Season | Episode | Episode Title | Name | Sentence | |
---|---|---|---|---|---|---|
0 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | waymar royce | What do you expect? They're savages. One lot s... |
1 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | will | I've never seen wildlings do a thing like this... |
2 | 2011-04-17 | Season 1 | Episode 1 | Winter is Coming | waymar royce | How close did you get? |
df.groupby(['Season','Episode'])['Sentence'].count().head()
Season Episode
Season 1 Episode 1 327
Episode 10 266
Episode 2 283
Episode 3 353
Episode 4 404
Name: Sentence, dtype: int64
df.set_index('Name').Sentence.str.split().str.len().groupby('Name').mean().sort_values(ascending = False).head(5)
Name
male singer 109.000000
slave owner 77.000000
manderly 62.000000
lollys stokeworth 62.000000
dothraki matron 56.666667
Name: Sentence, dtype: float64
s = pd.Series(df.Sentence.values, index = df.Name.shift(-1))
s.str.count('\?').groupby('Name').sum().sort_values(ascending = False).head()
Name
tyrion lannister 527
jon snow 374
jaime lannister 283
arya stark 265
cersei lannister 246
dtype: int64