处理缺失数据
创建一个含缺失值的Series
string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado'])
string_data.isnull()
通过isnull方法返回一个布尔型Series,缺失值显示为True。
通过索引可以将非缺失值设置为缺失值
string_data[0] = None
处理缺失数据的函数
dropna,除去缺失数据
fillna,用指定值填充缺失值
isnull,返回布尔型对象,缺失值显示True,其他显示False
notnull,isnull的否定式
滤除缺失数据
from numpy import nan as NA #从numpy模块中可以引入缺失值。
data = pd.Series([1,NA,3.5,NA,7]) #创建含缺失值的Series
data.dropna() #去除Series中的缺失值
data[data.notnull()] #与data.dropna()等价
对DataFrame对象,dropna方法默认丢弃所有含有缺失值的行。
data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA],
[NA, NA, NA], [NA, 6.5, 3.]])
cleaned = data.dropna()
传入参数how='all'则只除去整行都是NA的行。
cleaned = data.dropna(how='all')
传入参数axis=1或者axis='columns'则会改为逐列删除。
data = pd.DataFrame([[1., NA, 3.], [1., NA, NA],
[2, NA, NA], [3, NA, 3.]])
cleaned = data.dropna(axis=1)
cleaned2 = data.dropna(axis=1,how='all')
dropna的thresh参数,如设置thresh=2,表示如果某行至少有2个非缺失值时将该行保留下来
df = pd.DataFrame(np.random.randn(7,3)) #创建一个7行3列的DataFrame
df.iloc[:4,1] = NA #将数据框列1的前四行设置为缺失值
df.iloc[:2,2] = NA #将数据框列2的前两行设置为缺失值
df.dropna(thresh=2) #行数据有两个及以上非缺失值时将保留该行
df.dropna(thresh=3) #行数据有三个及以上非缺失值时将保留该行
填充缺失数据
Series和DataFrame都有fillna方法可以填充缺失数据
df.fillna(0)
fillna()方法可以传入字典,实现对不同列的缺失值用不同的值进行填充
df.fillna({1:0.5,2:0}) #列1的缺失值用0.5来填充,列2的缺失值用0来填充
若传入inplace=True参数,则可以实现就地修改(慎用)
df.fillna(0,inplace=True)
fillna传入method='ffill'参数,缺失值会以上方非缺失值为参数进行填充。
df = pd.DataFrame(np.random.randn(6, 3))
df.iloc[2:, 1] = NA
df.iloc[4:, 2] = NA
df.fillna(method='ffill')
传入limit参数可以限制填充缺失值数量。
df.fillna(method='ffill',limit=2) #限制一列只能填充两个缺失值
可以利用fillna实现许多别的功能,如用平均值来填充缺失值。
data = pd.Series([1,NA,3.5,NA,7])
data.fillna(data.mean())
fillna的参数:value、method、axis、inplace、limit
数据转换
移除重复数据
data = pd.DataFrame({'k1':['one','two'] * 3 + ['two'],'k2':[1,1,2,3,3,4,4]})
创建含重复数据的DataFrame。
duplicated()方法返回一个布尔型Series,重复值标记为True。
drop_duplicates()方法可以删去重复值。
duplicated和drop_duplicates都是默认判断全部列,也可以指定部分列。
data['v1'] = range(7) #增加一列
data.drop_duplicates(['k1']) #仅对k1列进行判断
默认情况下保留的是第一个出现的值组合,传入参数keep = 'last'则保留最后一个
利用函数或映射进行数据转换
data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',
'Pastrami', 'corned beef', 'Bacon',
'pastrami', 'honey ham', 'nova lox'],
'ounces': [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})
创建一个DataFrame
现在要添加一列表示food列的来源,先编写一个映射。
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}
food列中有大写有小写,先用str.lower()方法全部变成小写。
lowercased = data['food'].str.lower()
在DataFrame中创建一个新列aminal
data['aminal'] = lowercased.map(meat_to_animal)
右边表示将lowercased的每个元素到字典meat_to_animal中找到映射值,返回映射值的Series。
将上面的步骤合并简化
data['food'].str.lower().map(meat_to_animal)
替换值
data = pd.Series([1,-999,2,-999,-1000,3]) #创建一个Series
data.replace(-999,np.nan) #将其中的-999用缺失值来替换,非原地修改
被替换值和替换值都可以传入多个,通过列表形式传入。
传入的参数可以是字典,键表示被替换值,值表示替换值。
重命名轴索引
data = pd.DataFrame(np.arange(12).reshape((3, 4)),
index=['Ohio', 'Colorado', 'New York'],
columns=['one', 'two', 'three', 'four'])
transform = lambda x:x[:4].upper()
data.index.map(transform)
将data.index中每个元素作为参数传入transform函数中,即取每个元素的前四位并最大值。
可以将返回值赋值给index,对DataFrame的行索引进行就地修改
data.index = data.index.map(transform)
通过rename方法可以在原索引的基础上进行修改。
data.rename(index=str.title,columns=str.upper)
此代码表示将行索引每个元素首字母大写化,其余字母小写,列索引所有字母大写化,就地修改。
index和columns参数可以传入字典,字典的键是原索引,值是新索引。
data.rename(index={'OHIO': 'INDIANA'},
columns={'three': 'peekaboo'})
将指定原索引改为新索引,非原地修改。
传入inplace参数可以原地修改。
data.rename(index={'OHIO': 'INDIANA'}, inplace=True)
离散化和面元划分
现在有一组年龄数据
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
要将数据分为18-25,26-35,35-60及60以上的几个面元,通过pandas的cut方法可以实现。
bins = [18,25,35,60,100] #划分列表
cats = pd.cut(ages,bins)
给出了每个数字所在的区间合起来的列表。
返回的对象有一个codes属性,返回各个元素所在区间的顺序的列表。
如第一个元素20所在区间是18-25,该区间是第一个区间,所以用0表示。
categories属性返回所有区间。
value_counts属性计算各个区间分别有多少个元素。
默认情况下区间是左开右闭,可以通过在cut方法中传入参数right=False可以改为左闭右开。
cats = pd.cut(ages, bins,right=False)
cut方法中有个labels参数,传入一个列表,其中的元素可以作为区间的名称。
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
pd.cut(ages,bins,labels=group_names)
可以向cut方法传入需要分隔的区间个数而不是具体边界,程序会自动计算出区间大小。
data = np.random.rand(20)
pd.cut(data,4,precision=2)
将data数组分为4块,precision表示限定小数为两位。
qcut()方法是分位数切割方法。
data = np.random.randn(1000)
cats = pd.qcut(data,4)
表示将数据分为4块,每块的数据数量都是250个,即四分位。
通过pd.value_counts()方法统计各区间的数据数量。
可以自定义每块数据的数量,如传入列表[0,0.1,0.5,0.9,1.]作为参数,表示将数据分为四块,第一块占总数量的十分之一,第二块和第三块各占总数量的五分之二,最后一块占总数量的十分之一。
cats = pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
pd.value_counts(cats)
检测和过滤异常值
data = pd.DataFrame(np.random.randn(1000,4))
data.describe()
创建一个DataFrame,共四列,每列1000个数据,对DataFrame进行描述统计。
假如想找出某列中绝对值大小超过3的值
col = data[2]
col[np.abs(col) > 3]
传入一个布尔型数组作为索引。
要选出全部含有超过3或-3的值的行,可以在布尔型DataFrame中使用any方法
data[(np.abs(data) > 3).any(1)]
返回结果的每行中都有绝对值大于3的值,np.abs(data)>3返回的是布尔型DataFrame,对这个返回结果再执行any(1),表示筛选出行中有绝对值大于3的数值,返回布尔型Series。将这个布尔型Series作为data的索引,取出所有布尔值为True的值。
将值限制在区间-3~3之间
data[np.abs(data) > 3] = np.sign(data) * 3
data.describe()
第一行代码将data数据中绝对值大于3的数据设为3或-3。
sign方法根据数据是正、负、零,分别返回+1,-1,0。
np.sign(data).head()
排列和随即采样
numpy.random.permutation函数可以实现对Series或DataFrame的列的排列工作,随机重排序。通过需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组。
df = pd.DataFrame(np.arange(5 * 4).reshape((5,4)))
sampler = np.random.permutation(5)
sampler是将0-4随机排列的数组。
使用take函数对DataFrame的行进行重排序,顺序使用sampler
df.take(sampler)
可以看到行索引的顺序发生了改变。
如果不想用替换的方式选取随机子集,可以用sample方法,非原地修改
df.sample(n=3)
sample(n=3)表示在df中随机取三行。
要通过替换的方式产生样本(允许重复选择),传入参数replace=True。
choices = pd.Series([5, 7, -1, 6, 4])
draws = choices.sample(n=10, replace=True)
计算指标/哑变量
比如现在有一个三分类的分类变量A、B、C,设两个变量将这三个分类用数字表示,A用0,0表示,B用1,0表示,C用0,1表示。
程序中稍有不同,K个分类,会派生出k列矩阵。
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
pd.get_dummies(df['key'])
a用1,0,0表示,b用0,1,0表示,c用0,0,1表示。
如果想给列索引加一个前缀,可以通过get_dummies方法中的prefix参数实现
dumies = pd.get_dummies(df['key'],prefix='key')
df_with_dummy = df[['data1']].join(dummies) #选取df中的指定列,和dumies合并
若DataFrame中的某行同属于多个分类会比较复杂
mnames = ['movie_id', 'title', 'genres']
movies = pd.read_table('datasets/movielens/movies.dat', sep='::',
header=None, names=mnames) #读取数据,选择无数据头,用外部列表作为列名
movies[:10] #取数据的前十行
可以看到genres列中有多个电影类型。
将所有类型放到一个list中。
all_genres = []
for x in movies.genres:
all_genres.extend(x.split('|'))
genres = pd.unique(all_genres)
zero_matrix = np.zeros((len(movies),len(genres))) #创建多行多列的全0数组
dummies = pd.DataFrame(zero_matrix,columns=genres) #根据数组创建DataFrame,列名是genres中的元素
gen = movies.genres[0] #取出movies的genres列的第一行数据
gen.split('|') #拆分
dummies.columns.get_indexer(gen.split('|')) #返回拆分结果在dummies的列名中的排行
for i, gen in enumerate(movies.genres):
indices = dummies.columns.get_indexer(gen.split('|'))
dummies.iloc[i, indices] = 1
举个例子,将movies的genres列的第一行的电影类型对应的dummies中的列改为1。
例如genres列中第一行的电影类型是Animation、Children's,Comedy,上面的代码就是将dummies中的第一行对应的三列的数值改为1。
通过join方法将movies数据和加前缀的dummies数据合并起来
movies_windic = movies.join(dummies.add_prefix('Genre_'))
movies_windic.iloc[0].head(10) #取第一行的前十列看看
np.random.seed(12345) #设随机种子为12345
values = np.random.rand(10) #取10个在0-1之间的随机数字
bins = [0,0.2,0.4,0.6,0.8,1]
pd.get_dummies(pd.cut(values,bins))
pd.cut(values,bins)表示按bins的区间给values的值分区间
get_dummies是计算哑变量用的函数。
字符串操作
字符串对象方法
字符串split方法可以根据分隔符拆分字符串,返回列表
val = 'a,b, guido'
val.split(',')
split常与strip一起用,以除去空白符。
pieces = [x.strip() for x in val.split(',')]
用加法将子字符串用::连接起来,这种方法不常用。
first,second,third = pieces
first + '::' + second + '::' + third
更常用的方法是join方法。
'::'.join(pieces)
通过in方法判断某字符串中是否包含某字符。
通过index方法查找指定字符的位置,如果字符不存在会返回异常。
通过find方法查找指定字符位置,如果不存在则返回-1。
replace方法,将指定字符替换成其他字符。
python常用字符串方法
count,返回某字符在字符串中出现的次数。
endswith,startswith,判断字符串是否以某字符开头。
join,将某字符作为连接列表中多个字符串的分隔符
index,在字符串中找某字符的位置。
find同上。
rfind,找某字符在字符串中最后一次出现的位置,没有则返回-1。
replace,替换字符。
strip、rstrip、lstrip,去除空白符,换行符,制表符。
split,将字符串按某字符进行分隔。
lower,upper,将字符串小写化和大写化。
ljust、rjust,设定最低宽度限制,不足的用空格或者其他字符填充。
正则表达式
现有一个字符串,其中每几个字母间有若干个空格,现在要根据若干个字符进行拆分。
import re
text = "foo bar\t baz \tqux"
re.split('\s+',text)
\s+表示一个或多个空白符。
可以将匹配规则做成可重用对象。
regex = re.compile('\s+')
regex.split(text)
如果希望得到匹配regex的所有模式,可以使用findall方法
返回所有匹配的结果。
match和search跟findall的功能类似,findall是返回字符串中所有匹配项,search是返回第一个匹配项,match很严格,只匹配字符串的首部。
下面是对邮箱字符串的匹配。
text = """Dave [email protected]
Steve [email protected]
Rob [email protected]
Ryan [email protected]
"""
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}' #匹配规则
regex = re.compile(pattern,flags=re.IGNORECASE) #将匹配规则做成可重用对象,flags参数表示大小不敏感
使用findall将得到所有的邮箱。
regex.findall(text)
search会返回第一个匹配到的邮箱
regex.search(text)
结果只是告诉我们匹配对象在原字符串中起始和结束的位置。
m = regex.search(text)
text[m.start():m.end()]
regex.match将返回None,因为它只匹配出现在字符串开头的模式。
print(regex.match(text))
sub方法可以将匹配到的模式替换为指定字符串,返回得到的新字符串。
print(regex.sub('REDACTED',text))
如果想将匹配到的电子邮件地址分成三部分,用户名,域名和域名后缀,将匹配规则中指定区域用()括起来即可。
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex = re.compile(pattern, flags=re.IGNORECASE)
regex.findall(text)
m = regex.match('[email protected]')
m.groups()
match方法和search方法返回的结果都能用groups()方法,但findall()方法返回的不行,因为它返回的是一个元组列表。
sub还能通过\1、\2这样的形式访问匹配项的分组。
print(regex.sub(r'Username:\1,Domain:\2,Suffix:\3',text))
pandas的矢量化字符串函数
data = {'Dave': '[email protected]', 'Steve': '[email protected]',
'Rob': '[email protected]', 'Wes': np.nan}
data = pd.Series(data) #根据字典创建一个Series
data.isnull() #使用isnull方法返回一个Series,缺失值显示True,非缺失值显示False
data.str.contains('gmail')
通过Series的str.contains方法检查元素是否含有指定字符串。
也可以使用正则表达式
data.str.findall(pattern,flags=re.IGNORECASE)
取出data中的每个元素进行匹配,findall方法返回list。
matches = data.str.match(pattern,flags=re.IGNORECASE)
使用str.get方法可以获取列表元素。
matches.str.get(1) #获取列表的第二个元素
matches.str[0] #获取列表的第一个元素
因为matches中的数据是布尔值,所以取元素都是0缺失值。
因为data的数据是字符串,所以str[:5]选取前五个字符
还有更多pandas字符串方法。
以上内容主要出自:https://www.jianshu.com/p/ac7bec000dad