阅读量

原创教程,严禁转载。引用本文,请署名 Python中文网, http://www.zglg.work


我攥了很久才汇总出这篇小技巧合集,Pandas数据分析小技巧手册。

小技巧1:如何使用map对某些列做特征工程?

先生成数据:

d = {
"gender":["male", "female", "male","female"],
"color":["red", "green", "blue","green"],
"age":[25, 30, 15, 32]
}

df = pd.DataFrame(d)
df

gender 列上,使用 map 方法,快速完成如下映射:

d = {"male": 0, "female": 1}
df["gender2"] = df["gender"].map(d)

小技巧2: 使用 replace 和正则清洗数据

Pandas 的强项在于数据分析,自然就少不了数据清洗。

今天学习一个快速清洗数据的小技巧,在某列上使用 replace 方法和正则,快速完成值的清洗。

源数据:

d = {"customer": ["A", "B", "C", "D"],
"sales":[1100, "950.5RMB", "$400", " $1250.75"]}

df = pd.DataFrame(d)
df

打印结果:

customer    sales
0   A   1100
1   B   950.5RMB
2   C   $400
3   D   $1250.75

看到 sales 列的值,有整型,浮点型+RMB后变为字符串型,还有美元+整型,美元+浮点型。

我们的目标:清洗掉 RMB$ 符号,转化这一列为浮点型。

一行代码搞定:(点击代码区域,向右滑动,查看完整代码)

df["sales"] = df["sales"].replace("[$,RMB]","", regex = True).astype("float")

使用正则替换,将要替换的字符放到列表中 [$,RMB],替换为空字符,即 ""

最后使用 astype 转为 float

打印结果:

customer    sales
0   A   1100.00
1   B   950.50
2   C   400.00
3   D   1250.75

如果不放心,再检查下值的类型:

df["sales"].apply(type)

打印结果:

0    <class 'float'>
1    <class 'float'>
2    <class 'float'>
3    <class 'float'>

小技巧3:使用 melt 如何对数据透视分析?

构造一个 DataFrame:

d = {\
"district_code": [12345, 56789, 101112, 131415],
"apple": [5.2, 2.4, 4.2, 3.6],
"banana": [3.5, 1.9, 4.0, 2.3],
"orange": [8.0, 7.5, 6.4, 3.9]
}

df = pd.DataFrame(d)
df

打印结果:

district_code   apple   banana  orange
0123455.23.58.0
1567892.41.97.5
21011124.24.06.4
31314153.62.33.9

5.2 表示 12345 区域的 apple 价格,并且 apple, banana, orange,这三列都是一种水果,那么如何把这三列合并为一列?

使用 pd.melt

具体参数取值,大家根据此例去推敲:

df = df.melt(\
id_vars = "district_code",
var_name = "fruit_name",
value_name = "price")
df

打印结果:

district_code   fruit_name  price
012345  apple   5.2
156789  apple   2.4
2101112 apple   4.2
3131415 apple   3.6
412345  banana  3.5
556789  banana  1.9
6101112 banana  4.0
7131415 banana  2.3
812345  orange  8.0
956789  orange  7.5
10101112    orange  6.4
11131415    orange  3.9

以上就是长 DataFrame,对应的原 DataFrame 是宽 DF.

小技巧4:已知 year 和 dayofyear,怎么转 datetime?

原 DataFrame

d = {"year": [2019, 2019, 2020],"day_of_year": [350, 365, 1]}
df = pd.DataFrame(d)
df

打印结果:

  year  day_of_year
0 2019 350
1 2019 365
2 2020 1

转 datetime 的 trick

步骤 1: 创建整数

df["int_number"] = df["year"]*1000 + df["day_of_year"]

打印 df 结果:

year    day_of_year int_number
0  2019 350 2019350
1  2019 365 2019365
2  2020 1  2020001

步骤 2: to_datetime

df["date"]=pd.to_datetime(df["int_number"],
format = "%Y%j")

注意 "%Y%j" 中转化格式 j

打印结果:

    year    day_of_year int_number  date
0 2019350 2019350 2019-12-16
1 2019365 2019365 2019-12-31
2 20201 2020001 2020-01-01

小技巧5:如何将分类中出现次数较少的值归为 others?

这也是我们在数据清洗、特征构造中面临的一个任务。

如下一个 DataFrame:

d = {"name":['Jone','Alica','Emily','Robert','Tomas',
             'Zhang','Liu','Wang','Jack','Wsx','Guo'],
     "categories": ["A", "C", "A", "D", "A", 
                    "B", "B", "C", "A", "E", "F"]}
df = pd.DataFrame(d)
df

结果:

    name    categories
0   Jone    A
1   Alica   C
2   Emily   A
3   Robert  D
4   Tomas   A
5   Zhang   B
6   Liu B
7   Wang    C
8   Jack    A
9   Wsx E
10  Guo F

D、E、F 仅在分类中出现一次,A 出现次数较多。

步骤 1:统计频次,并归一

frequencies = df["categories"].value_counts(normalize = True)
frequencies

结果:

A    0.363636
B    0.181818
C    0.181818
F    0.090909
E    0.090909
D    0.090909
Name: categories, dtype: float64

步骤 2:设定阈值,过滤出频次较少的值

threshold = 0.1
small_categories = frequencies[frequencies < threshold].index
small_categories

结果:

Index(['F', 'E', 'D'], dtype='object')

步骤 3:替换值

df["categories"] = df["categories"] \
.replace(small_categories, "Others")

替换后的 DataFrame:

    name    categories
0   Jone    A
1   Alica   C
2   Emily   A
3   Robert  Others
4   Tomas   A
5   Zhang   B
6   Liu B
7   Wang    C
8   Jack    A
9   Wsx Others
10  Guo Others

小技巧6:如何快速找出 DataFrame 所有列 null 值个数?

实际使用的数据,null 值在所难免。如何快速找出 DataFrame 所有列的 null 值个数?

使用 Pandas 能非常方便实现,只需下面一行代码:

data.isnull().sum()

data.isnull(): 逐行逐元素查找元素值是否为 null.

.sum(): 默认在 axis 为 0 上完成一次 reduce 求和。

上手实际数据,使用这个小技巧,很爽。

读取泰坦尼克预测生死的数据集

data = pd.read_csv('titanicdataset-traincsv/train.csv')

结果:

img

检查 null 值:

data.isnull().sum()

结果:

PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Age 列 177 个 null 值

Cabin 列 687 个 null 值

Embarked 列 2 个 null 值

小技巧7:如何处理和填充空值?

np.nan 是 pandas 中常见空值,使用 dropna 过滤空值,axis 0 表示按照行,1 表示按列,how 默认为 any ,意思是只要有一个 nan 就过滤某行或某列,all 所有都为 nan

# axis 0 表示按照行,all 此行所有值都为 nan
df.dropna(axis=0, how='all')

充填空值

空值一般使用某个统计值填充,如平均数、众数、中位数等,使用函数 fillna:

# 使用a列平均数填充列的空值,inplace true表示就地填充
df["a"].fillna(df["a"].mean(), inplace=True)

小技巧8:如何用 Pandas 快速生成时间序列数据?

与时间序列相关的问题,平时还是挺常见的。

今天介绍一个小技巧,使用 pd.util.testing.makeTimeDataFrame

只需要一行代码,便能生成一个 index 为时间序列的 DataFrame:

import pandas as pd

pd.util.testing.makeTimeDataFrame(10)

结果:

A   B   C   D
2000-01-03  0.932776    -1.509302   0.285825    0.941729
2000-01-04  0.565230    -1.598449   -0.786274   -0.221476
2000-01-05  -0.152743   -0.392053   -0.127415   0.841907
2000-01-06  1.321998    -0.927537   0.205666    -0.041110
2000-01-07  0.324359    1.512743    0.553633    0.392068
2000-01-10  -0.566780   0.201565    -0.801172   -1.165768
2000-01-11  -0.259348   -0.035893   -1.363496   0.475600
2000-01-12  -0.341700   -1.438874   -0.260598   -0.283653
2000-01-13  -1.085183   0.286239    2.475605    -1.068053
2000-01-14  -0.057128   -0.602625   0.461550    0.033472

时间序列的间隔还能配置,默认的 A B C D 四列也支持配置。

import numpy as np

df = pd.DataFrame(np.random.randint(1,1000,size=(10,3)),
                  columns = ['商品编码','商品销量','商品库存'])
df.index = pd.util.testing.makeDateIndex(10,freq='H')

结果:

    商品编码    商品销量    商品库存
2000-01-01 00:00:00 99  264 98
2000-01-01 01:00:00 294 406 827
2000-01-01 02:00:00 89  221 931
2000-01-01 03:00:00 962 153 956
2000-01-01 04:00:00 538 46  374
2000-01-01 05:00:00 226 973 750
2000-01-01 06:00:00 193 866 7
2000-01-01 07:00:00 300 129 474
2000-01-01 08:00:00 966 372 835
2000-01-01 09:00:00 687 493 910

小技巧9:如何重新排序 DataFrame 的列?

某些场景需要重新排序 DataFrame 的列,如下 DataFrame:

如何将列快速变为:

下面给出 2 种简便的小技巧。先构造数据:

df = pd.DataFrame(np.random.randint(0,20,size=(5,7)) \
,columns=list('ABCDEFG'))
df

方法1,直接了当:

df2 = df[["A", "C", "D", "F", "E", "G", "B"]]
df2

结果:

方法2,也了解下:

cols = df.columns[[0, 2 , 3, 5, 4, 6, 1]]
df3 = df[cols]
df3

也能得到方法1的结果。

小技巧10:如何完成数据下采样,调整步长由小时为天?

步长为小时的时间序列数据,有没有小技巧,快速完成下采样,采集成按天的数据呢?

先生成测试数据:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randint(1,10,size=(240,3)), \
columns = ['商品编码','商品销量','商品库存'])

df.index = pd.util.testing.makeDateIndex(240,freq='H')
df

生成 240 行步长为小时间隔的数据:

小技巧,使用 resample 方法,合并为天(D)

day_df = df.resample("D")["商品销量"].sum().to_frame()
day_df

结果如下,10行,240小时,正好为 10 days:

小技巧11: 100G 数据如何先随机读取1%?

对于动辄就几十或几百个 G 的数据,在读取的这么大数据的时候,我们有没有办法随机选取一小部分数据,然后读入内存,快速了解数据和开展 EDA ?

使用 Pandas 的 skiprows 和 概率知识,就能做到。

下面解释具体怎么做。

如下所示,读取某 100 G 大小的 big_data.csv 数据

  1. 使用 skiprows 参数,
  2. x > 0 确保首行读入,
  3. np.random.rand() > 0.01 表示 99% 的数据都会被随机过滤掉

言外之意,只有全部数据的 1% 才有机会选入内存中。

import pandas as pd
import numpy as np

df = pd.read_csv("big_data.csv",
skiprows =
lambda x: x>0and np.random.rand() > 0.01)

print("The shape of the df is {}.
It has been reduced 100 times!".format(df.shape))

使用这种方法,读取的数据量迅速缩减到原来的 1% ,对于迅速展开数据分析有一定的帮助。

小技巧12:通过标准差如何找出异常值?

找出异常值常用标准差法,异常值是平均值上下1.96个标准差区间以外的值

import pandas as pd

df = pd.DataFrame({'a':[1,3,np.nan],'b':[4,np.nan,np.nan]})

# 异常值平均值上下1.96个标准差区间以外的值
meangrade = df['a'].mean()
stdgrade = df['a'].std()
toprange = meangrade + stdgrade * 1.96
botrange = meangrade - stdgrade * 1.96

# 过滤区间外的值
copydf = df
copydf = copydf.drop(copydf[copydf['a']
        > toprange].index)
copydf = copydf.drop(copydf[copydf['a']
        < botrange].index)
copydf

小技巧13:通过分位数如何找出异常值?

分位数法:小于 1/4分位数减去 1/4和3/4分位数差的1.5倍,大于3/4减去 1/4和3/4分位数差的1.5倍,都为异常值

q1 = df['a'].quantile(.25)
q3 = df['a'].quantile(.75)
iqr = q3-q1
toprange = q3 + iqr * 1.5
botrange = q1 - iqr * 1.5

copydf = df
copydf = copydf.drop(copydf[copydf['a']
        > toprange].index)
copydf = copydf.drop(copydf[copydf['a']
        < botrange].index)
copydf

小技巧14:如何修复非法值?

假如某门课最高分100,如果出现 -2, 120 这样的值,显然不合理,使用布尔类型的Series对象修改数值:

df.loc[(df['a'] < -2,'a')] = 0
df.loc[(df['a'] >= 100,'a')] = 100

小技巧15:如何过滤重复值?

过滤某列重复值,使用 drop_duplicates 方法,第一个参数为列名,keep关键字等于last:最后一次出现此值行:

df.drop_duplicates(['Names'], keep='last')

小技巧16:如何使用 apply 去掉特殊字符?

某列单元格含有特殊字符,如标点符号,使用元素级操作方法 apply 干掉它们:

import string
exclude = set(string.punctuation)

def remove_punctuation(x):
    x = ''.join(ch for ch in x if ch not in exclude)
    return x
# 原df
Out[26]: 
      a       b
0   c,d  edc.rc
1     3       3
2  d ef       4

# 过滤a列标点
In [27]: df.a = df.a.apply(remove_punctuation) 
In [28]: df                
Out[28]: 
      a       b
0    cd  edc.rc
1     3       3
2  d ef       4

小技巧17:如何使用 cut 做数据分箱?

将百分制分数转为A,B,C,D四个等级,bins 被分为 [0,60,75,90,100],labels 等于['D', 'C', 'B', 'A']:

# 生成20个[0,100]的随机整数
In [30]: a = np.random.randint(1,100,20)                   
In [31]: a                                    
Out[31]: 
array([48, 22, 46, 84, 13, 52, 36, 35, 27,
       99, 31, 37, 15, 31,  5, 46, 98,99, 60, 43])

# cut分箱
In [33]: pd.cut(a, [0,60,75,90,100], labels = ['D', 'C', 'B', 'A'])             
Out[33]: 
[D, D, D, B, D, ..., D, A, A, D, D]
Length: 20
Categories (4, object): [D < C < B < A]

小技巧18:如何使用 rank 做排名?

rank 方法,生成数值排名,ascending 为False,分值越大,排名越靠前:

In [36]: df = pd.DataFrame({'a':[46, 98,99, 60, 43]} )) 
In [53]: df['a'].rank(ascending=False)                   
Out[53]: 
0    4.0
1    2.0
2    1.0
3    3.0
4    5.0

小技巧19:如何使用 category列转数值?

某列取值只可能为有限个枚举值,往往需要转为数值,使用get_dummies,或自己定义函数:

pd.get_dummies(df['a'])

自定义函数,结合 apply:

def c2n(x):
    if x=='A':
        return 95
    if x=='B':
        return 80

df['a'].apply(c2n)

小技巧 20:如何快速拿到数据最多的 3 个分类?

读入数据:

df = pd.read_csv("IMDB-Movie-Data.csv")
df

1000 行数据,genre 取值的频次统计如下:

vc = df["genre"].value_counts()
vc

打印结果:

Action,Adventure,Sci-Fi       50
Drama                         48
Comedy,Drama,Romance          35
Comedy                        32
Drama,Romance                 31
                              ..
Adventure,Comedy,Fantasy       1
Biography,History,Thriller     1
Action,Horror                  1
Mystery,Thriller,Western       1
Animation,Fantasy              1
Name: genre, Length: 207, dtype: int64

筛选出 top3 的 index:

top_genre = vc[0:3].index
print(top_genre)

打印结果:

Index(['Action,Adventure,Sci-Fi', 'Drama', \
       'Comedy,Drama,Romance'], dtype='object')

使用得到的 top3 的 index ,结合 isin,选择出相应的 df

df_top = df[df["genre"].isin(top_genre)]
df_top

结果:

小技巧21:如何使用 count 统计词条出现次数?

读入 IMDB-Movie-Data 数据集,1000行数据:

df = pd.read_csv("../input/imdb-data/IMDB-Movie-Data.csv")
df['Title']

打印 Title 列:

0      Guardians of the Galaxy
1                   Prometheus
2                        Split
3                         Sing
4                Suicide Squad
                ...
995       Secret in Their Eyes
996            Hostel: Part II
997     Step Up 2: The Streets
998               Search Party
999                 Nine Lives
Name: Title, Length: 1000, dtype: object

标题是由几个单词组成,用空格分隔。

df["words_count"] = df["Title"].str.count(" ") + 1
df[["Title","words_count"]]

后续更多小技巧陆续发布迭代中...