pandas 數據分析

from pandas import Series, DataFrame
import pandas as pd

參考:
https://amaozhao.gitbooks.io/pandas-notebook/content/
https://github.com/justmarkham/pandas-videos/blob/master/pandas.ipynb

創建series和dataframe

1.創建series

通過列表創建

obj = Series([4, 7, -5, 3]) 
obj2 = Series([4, 7, -5, 3], index=['d','b','a','c']) #指定索引

通過字典創建

sdata = {'Ohio':35000, 'Texas':7100, 'Oregon':1600,'Utah':500}
obj3 = Series(sdata)

通過字典加索引

states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = Series(sdata, index=states)

2.創建dataframe

詞典生成

data = {'state':['Ohio', 'Ohio', 'Ohio', 'Nevada','Nevada'],
        'year':[2000, 2001, 2002, 2011, 2002],
        'pop':[1.5, 1.7, 3.6, 2.4, 2.9]}
frame = DataFrame(data)
frame2 = DataFrame(data, columns=['year', 'state', 'pop']) #指定列
frame3 = DataFrame(data, columns=['year', 'state', 'pop'],
         index=['one', 'two', 'three', 'four', 'five']) #指定列和索引

列表生成(常用)

errors = [('c',1,'right'), ('b', 2,'wrong')]
df = pd.DataFrame(errors,columns=['name', 'count', 'result'])

嵌套詞典

pop = {'Nevada':{2001:2.4, 2002:2.9},
       'Ohio':{2000:1.5, 2001:1.7, 2002:3.6}}
frame4 = DataFrame(pop)
Out[138]:
      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7
2002     2.9   3.6

series組合

In [4]: a = pd.Series([1,2,3]) 
In [5]: b = pd.Series([2,3,4])
In [6]: c = pd.DataFrame([a,b]) 
In [7]: c
Out[7]:
   0  1  2
0  1  2  3
1  2  3  4
#或者也可以按照列來生成dataframe
c = pd.DataFrame({'a':a,'b':b})

索引和選取

對于一組數據:

data = DataFrame(np.arange(16).reshape((4,4)),index=['Ohio', 'Colorado','Utah','New York'],columns=['one','two','three','four'])
>>> data
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

幾種索引方式

事實上有很多種索引方式,ix是較老的方式

data['two']
data.ix['Ohio']
data.ix['Ohio', ['two','three']]

較新的是loc與iloc
loc用于索引中有文字的,iloc僅僅用于索引為數字的
然后比較坑的是經常是索引不成功,
對于列來說:這時候要檢查下這個名字對不對?可以用
data.columns=[]來強行規定名字然后再索引
對于行來說:可以強行reset_index或者set_index為新的一組值,基本可以避免大坑

按照索引來遍歷

按照行來進行,這個操作其實很低效率

lbsdata=pd.read_csv('testlocation.csv',sep='\t')
lbstagRow = pd.DataFrame()
lbstag = pd.DataFrame()
for index, row in lbsdata.iterrows():
    lbstagRow['recommend'] = [jsonToSp['formatted_address']]
    lbstagRow['city'] = [jsonToSparse['addressComponent']]
    lbstag = lbstag.append(lbstagRow,ignore_index=True)

也可以:

for ix, row in df.iterrows():
for ix, col in df.iteritems():
for i in range(0,len(df)):

排序

按列排序

In[73]: obj = Series(range(4), index=['d','a','b','c'])
In[74]: obj.sort_index()  
Out[74]: 
a    1
b    2
c    3
d    0
dtype: int64
In[78]: frame = DataFrame(np.arange(8).reshape((2,4)),index=['three', 'one'],columns=['d','a','b','c'])
In[79]: frame
Out[79]: 
       d  a  b  c
three  0  1  2  3
one    4  5  6  7
In[86]: frame.sort_index()
Out[86]: 
       d  a  b  c
one    4  5  6  7
three  0  1  2  3
In[87]: frame.sort()
Out[87]: 
       d  a  b  c
one    4  5  6  7
three  0  1  2  3

按行排序

In[89]: frame.sort_index(axis=1, ascending=False)
Out[89]: 
       d  c  b  a
three  0  3  2  1
one    4  7  6  5

按值排序

frame.sort_values('a')

刪除

刪除指定軸上的項

即刪除 Series 的元素或 DataFrame 的某一行(列)的意思,通過對象的 .drop(labels, axis=0) 方法:

In[11]: ser = Series([4.5,7.2,-5.3,3.6], index=['d','b','a','c'])
In[13]: ser.drop('c')
Out[13]: 
d    4.5
b    7.2
a   -5.3
dtype: float64

刪除行和列

In[17]: df = DataFrame(np.arange(9).reshape(3,3), index=['a','c','d'], columns=['oh','te','ca'])
In[18]: df
Out[18]: 
   oh  te  ca
a   0   1   2
c   3   4   5
d   6   7   8
In[19]: df.drop('a')
Out[19]: 
   oh  te  ca
c   3   4   5
d   6   7   8
In[20]: df.drop(['oh','te'],axis=1)
Out[20]: 
   ca
a   2
c   5
d   8

刪除點

df_train.sort_values(by = 'GrLivArea',ascending = False)[:2]
df_train = df_train.drop(df_train[df_train['Id'] == 1299].index)
df_train = df_train.drop(df_train[df_train['Id'] == 524].index)

DataFrame連接

算術運算

In[5]: df1 = DataFrame(np.arange(12.).reshape((3,4)),columns=list('abcd'))
In[6]: df2 = DataFrame(np.arange(20.).reshape((4,5)),columns=list('abcde'))
In[9]: df1+df2
Out[9]: 
    a   b   c   d   e
0   0   2   4   6 NaN
1   9  11  13  15 NaN
2  18  20  22  24 NaN
3 NaN NaN NaN NaN NaN
# 傳入填充值
In[11]: df1.add(df2, fill_value=0)
Out[11]: 
    a   b   c   d   e
0   0   2   4   6   4
1   9  11  13  15   9
2  18  20  22  24  14
3  15  16  17  18  19

merge

pandas.merge可根據一個或多個鍵將不同DataFrame中的行連接起來。

默認情況下,merge做的是“inner”連接,結果中的鍵是交集,其它方式還有“left”,“right”,“outer”。“outer”外連接求取的是鍵的并集,組合了左連接和右連接。

In[14]: df1 = DataFrame({'key':['b','b','a','c','a','a','b'],'data1':range(7)})
In[15]: df2 = DataFrame({'key':['a','b','d'],'data2':range(3)})
In[18]: pd.merge(df1, df2)  #或顯式: pd.merge(df1, df2, on='key')
Out[18]: 
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0

concat

這種數據合并運算被稱為連接(concatenation)、綁定(binding)或堆疊(stacking)。默認情況下,concat是在axis=0(行)上工作的,最終產生一個新的Series。如果傳入axis=1(列),則變成一個DataFrame。

pd.concat([s1,s2,s3], axis=1)
dfs = []
for classify in classify_finance + classify_other:
    sql = "select classify, tags from {} where classify='{}' length(tags)>0 limit 1000".format(mysql_table_sina_news_all, classify)
    df = pd.read_sql(sql,engine)
    dfs.append(df)
df_all = pd.concat(dfs, ignore_index=True)

數據轉換

數據過濾、清理以及其他的轉換工作。

移除重復數據(去重)

duplicated()
DataFrame的duplicated方法返回一個布爾型Series,表示各行是否是重復行

df = DataFrame({'k1':['one']*3 + ['two']*4, 'k2':[1,1,2,3,3,4,4]})
df.duplicated()
df.drop_duplicates()

利用函數或映射進行數據轉換

對于數據:

df = DataFrame({'food':['bacon','pulled pork','bacon','Pastraml','corned beef', 'Bacon', 'pastraml','honey ham','nova lox'],'ounces':[4,3,12,6,7.5,8,3,5,6]})

增加一列表示該肉類食物來源的動物類型,先編寫一個肉類到動物的映射:

meat_to_animal = {'bacon':'pig',
    'pulled pork':'pig',
    'pastraml':'cow',
    'corned beef':'cow',
    'honey ham':'pig',
    'nova lox':'salmon'}

map

Series的map方法可以接受一個函數或含有映射關系的字典型對象。

df['animal'] = df['food'].map(str.lower).map(meat_to_animal)
df['food'].map(lambda x : meat_to_animal[x.lower()])
Out[22]: 
0       pig
1       pig
2       pig
3       cow
4       cow
5       pig
6       cow
7       pig
8    salmon

apply 和 applymap

對于DataFrame:

In[21]: df = DataFrame(np.random.randn(4,3), columns=list('bde'),index=['Utah','Ohio','Texas','Oregon'])
In[22]: df
Out[22]: 
               b         d         e
Utah    1.654850  0.594738 -1.969539
Ohio    2.178748  1.127218  0.451690
Texas   1.209098 -0.604432 -1.178433
Oregon  0.286382  0.042102 -0.345722

apply將函數應用到由各列或行所形成的一維數組上。

作用到列:

In[24]: f = lambda x : x.max() - x.min()
In[25]: df.apply(f)
Out[25]: 
b    1.892366
d    1.731650
e    2.421229
dtype: float64

作用到行

In[26]: df.apply(f, axis=1)
Out[26]: 
Utah      3.624390
Ohio      1.727058
Texas     2.387531
Oregon    0.632104
dtype: float64

作用到每個元素

In[70]: frame = DataFrame(np.random.randn(4,3), columns=list('bde'),index=['Utah','Ohio','Texas','Oregon'])
In[72]: frame.applymap(lambda x : '%.2f' % x)
Out[72]: 
            b      d      e
Utah     1.19   1.56  -1.13
Ohio     0.10  -1.03  -0.04
Texas   -0.22   0.77  -0.73
Oregon   0.22  -2.06  -1.25

numpy的ufuncs
Numpy的ufuncs(元素級數組方法)也可用于操作pandas對象。
取絕對值操作np.abs(),np.max(),np.min()

替換值

In[23]: se = Series([1, -999, 2, -999, -1000, 3])
In[24]: se.replace(-999, np.nan)
Out[24]: 
0       1
1     NaN
2       2
3     NaN
4   -1000
5       3
dtype: float64
In[25]: se.replace([-999, -1000], np.nan)
Out[25]: 
0     1
1   NaN
2     2
3   NaN
4   NaN
5     3
dtype: float64
In[26]: se.replace([-999, -1000], [np.nan, 0])
Out[26]: 
0     1
1   NaN
2     2
3   NaN
4     0
5     3
dtype: float64
# 字典
In[27]: se.replace({-999:np.nan, -1000:0})
Out[27]: 
0     1
1   NaN
2     2
3   NaN
4     0
5     3
dtype: float64

df.rename(index=str.title, columns=str.upper)
df.rename(index={'OHIO':'INDIANA'}, columns={'three':'peekaboo'})

離散化和面元劃分

pd.cut

為了便于分析,連續數據常常離散化或拆分為“面元”(bin)。比如:

ages = [20, 22,25,27,21,23,37,31,61,45,41,32]

需要將其劃分為“18到25”, “26到35”,“36到60”以及“60以上”幾個面元。要實現該功能,需要使用pandas的cut函數。

bins = [18, 25, 35, 60, 100]
cats = pd.cut(ages, bins)
Out[39]: 
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, object): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]

可以通過right=False指定哪端是開區間或閉區間。也可以指定面元的名稱:

cats = pd.cut(ages, bins, right=False)
group_name = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
cats = pd.cut(ages, bins, labels=group_name)
pd.value_counts(cats)
Out[46]: 
Youth         5
MiddleAged    3
YoungAdult    3
Senior        1
dtype: int64

pd.qcut

qcut是一個非常類似cut的函數,它可以根據樣本分位數對數據進行面元劃分,根據數據的分布情況,cut可能無法使各個面元中含有相同數量的數據點,而qcut由于使用的是樣本分位數,可以得到大小基本相等的面元。

In[48]: data = np.random.randn(1000)
In[49]: cats = pd.qcut(data, 4)
In[50]: cats
Out[50]: 
[(0.577, 3.564], (-0.729, -0.0341], (-0.729, -0.0341], (0.577, 3.564], (0.577, 3.564], ..., [-3.0316, -0.729], [-3.0316, -0.729], (-0.0341, 0.577], [-3.0316, -0.729], (-0.0341, 0.577]]
Length: 1000
Categories (4, object): [[-3.0316, -0.729] < (-0.729, -0.0341] < (-0.0341, 0.577] < (0.577, 3.564]]
In[51]: pd.value_counts(cats)
Out[51]: 
(0.577, 3.564]       250
(-0.0341, 0.577]     250
(-0.729, -0.0341]    250
[-3.0316, -0.729]    250
dtype: int64

檢測和過濾異常值

可以先describe然后在找出超過某一個閾值的

data[(np.abs(data)>3).any(1)]
data[np.abs(data)>3] = np.sign(data)*3
In[63]: data.describe()
Out[63]: 
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.067623     0.068473     0.025153    -0.002081
std       0.995485     0.990253     1.003977     0.989736
min      -3.000000    -3.000000    -3.000000    -3.000000
25%      -0.774890    -0.591841    -0.641675    -0.644144
50%      -0.116401     0.101143     0.002073    -0.013611
75%       0.616366     0.780282     0.680391     0.654328
max       3.000000     2.653656     3.000000     3.000000

單因素分析

這里的關鍵在于如何建立閾值,定義一個觀察值為異常值。我們對數據進行正態化,意味著把數據值轉換成均值為0,方差為1的數據。

saleprice_scaled= StandardScaler().fit_transform(df_train['SalePrice'][:,np.newaxis]);
low_range = saleprice_scaled[saleprice_scaled[:,0].argsort()][:10]
high_range= saleprice_scaled[saleprice_scaled[:,0].argsort()][-10:]
print('outer range (low) of the distribution:')
print(low_range)
print('\nouter range (high) of thedistribution:')
print(high_range)

進行正態化后,可以看出:

低范圍的值都比較相似并且在0附近分布。
高范圍的值離0很遠,并且七點幾的值遠在正常范圍之外。

雙變量分析

'GrLivArea'和'SalePrice'雙變量分析

var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

相關性探索

與數字型變量的關系

var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

與類別型變量的關系

var = 'OverallQual'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000);

相關性分析

相關系數矩陣

corrmat = df_train.corr()
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corrmat, vmax=.8, square=True);

最近的相關系數矩陣

k = 10 #number ofvariables for heatmap
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(df_train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, 
yticklabels=cols.values, xticklabels=cols.values)
plt.show()

兩兩之間的散點圖

sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea','GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(df_train[cols], size = 2.5)
plt.show();

缺失數據

關于缺失數據需要思考的重要問題:
這一缺失數據的普遍性如何?
缺失數據是隨機的還是有律可循?
這些問題的答案是很重要的,因為缺失數據意味著樣本大小的縮減,這會阻止我們的分析進程。除此之外,以實質性的角度來說,我們需要保證對缺失數據的處理不會出現偏離或隱藏任何難以忽視的真相。

total= df_train.isnull().sum().sort_values(ascending=False)
percent = (df_train.isnull().sum()/df_train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total','Percent'])
missing_data.head(20)

當超過15%的數據都缺失的時候,我們應該刪掉相關變量且假設該變量并不存在。

根據這一條,一系列變量都應該刪掉,例如'PoolQC', 'MiscFeature', 'Alley'等等,這些變量都不是很重要,因為他們基本都不是我們買房子時會考慮的因素。

df_train= df_train.drop((missing_data[missing_data['Total'] > 1]).index,1)
df_train= df_train.drop(df_train.loc[df_train['Electrical'].isnull()].index)
df_train.isnull().sum().max() #justchecking that there's no missing data missing..

多元分析

正態性:

應主要關注以下兩點:
直方圖 - 峰度和偏度。
正態概率圖 - 數據分布應緊密跟隨代表正態分布的對角線。

  1. ‘SalePrice’

繪制直方圖和正態概率圖:

sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

可以看出,房價分布不是正態的,顯示了峰值,正偏度,但是并不跟隨對角線。
可以用對數變換來解決這個問題

進行對數變換:

df_train['SalePrice']= np.log(df_train['SalePrice'])

繪制變換后的直方圖和正態概率圖:

sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

同方差性:

最好的測量兩個變量的同方差性的方法就是圖像。

  1. 'SalePrice' 和 'GrLivArea'同方差性

繪制散點圖:
plt.scatter(df_train['GrLivArea'],df_train['SalePrice']);

虛擬變量

將類別變量轉換為虛擬變量:
df_train = pd.get_dummies(df_train)

def factor(series):
    levels = list(set(series))
    design_matrix = np.zeros((len(series), len(levels)))
    for row_index, elem in enumerate(series):
        design_matrix[row_index, levels.index(elem)] = 1
    name = series.name or ""
    columns = map(lambda level: "%s[%s]" % (name, level), levels)
    df = pd.DataFrame(design_matrix, index=series.index,
                      columns=columns)
    return df

在處理類別變量的時候可以:

data.apply(lambda x: sum(x.isnull()))
var = ['Gender','Salary_Account','Mobile_Verified','Var1','Filled_Form','Device_Type','Var2','Source']
for v in var:
    print '\nFrequency count for variable %s'%v
    print data[v].value_counts()
print len(data['City'].unique())
#drop city because too many unique
data.drop('City',axis=1,inplace=True)

更加簡單的方法是:

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
var_to_encode = ['Device_Type','Filled_Form','Gender','Var1','Var2','Mobile_Verified','Source']
for col in var_to_encode:
    data[col] = le.fit_transform(data[col])
data = pd.get_dummies(data, columns=var_to_encode)
data.columns

處理時間變量

data['Age'] = data['DOB'].apply(lambda x: 115 - int(x[-2:]))
data['Age'].head()

df_train.onlineDate=pd.to_datetime(df_train.onlineDate)
df_train['age']=(datetime.date.today()-df_train.onlineDate).dt.days

詞向量化

# import and init CountVectorizer
from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer()
vectorizer # default parameters

CountVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=<class 'numpy.int64'>, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), preprocessor=None, stop_words=None,
        strip_accents=None, token_pattern='(?u)\\b\\w\\w+\\b',
        tokenizer=None, vocabulary=None)
# transform text into document-term matrix

X = vectorizer.fit_transform(reviews)
print(type(X)) # X is a sparse matrix
print(X)

處理百分號

p_float = df['p_str'].str.strip("%").astype(float)/100;
p_float_2 = p_float.round(decimals=2)

p_str_2 = p_float.apply(lambda x: format(x, '.2%')); 

特征離散化

s_amount_C = df_lc_C['借款金額']
q = [0.05, 0.2, 0.5, 0.8, 0.95]
bins = s_amount_C.quantile(q)
s_fea_dct_amount_C = pd.cut(s_amount_C, add_ends_to_bins(bins.values))
dict_s_fea_dct['amount'] = s_fea_dct_amount_C

s_term_C = df_lc_C['借款期限']
q = [0.05, 0.2, 0.5, 0.8, 0.95]
bins = s_term_C.quantile(q)
sns.countplot(s_term_C);

看有多少個類別

def add_ends_to_bins(bins):
    return np.append(np.insert(bins, 0, -np.inf), np.inf)
    
df_lc_C.groupby(s_type_C).size()
for col in category:
print(df_train2[str(col)].value_counts(dropna=False))
sns.countplot(s_is_first_C);

處理不同類別變量

DataFrame.convert_objects(convert_dates=True, convert_numeric=False, convert_timedeltas=True, copy=True)
這個命令非常好用!

使效率倍增的Pandas使用技巧

本文取自Analytics Vidhya的一個帖子12 Useful Pandas Techniques in Python for Data Manipulation,瀏覽原帖可直接點擊鏈接,中文版可參見Datartisan的用 Python 做數據處理必看:12 個使效率倍增的 Pandas 技巧。這里主要對帖子內容進行檢驗并記錄有用的知識點。

數據集

首先這個帖子用到的數據集是datahack的貸款預測(load prediction)競賽數據集,點擊鏈接可以訪問下載頁面,如果失效只需要注冊后搜索loan prediction競賽就可以找到了。入坑的感興趣的同學可以前往下載數據集,我就不傳上來github了。

先簡單看一看數據集結構(此處表格可左右拖動):

Loan_ID Gender Married Dependents Education Self_Employed ApplicantIncome CoapplicantIncome LoanAmount Loan_Amount_Term Credit_History Property_Area Loan_Status
LP001002 Male No 0 Graduate No 5849 0 360 1 Urban Y
LP001003 Male Yes 1 Graduate No 4583 1508 128 360 1 Rural N
LP001005 Male Yes 0 Graduate Yes 3000 0 66 360 1 Urban Y
LP001006 Male Yes 0 Not Graduate No 2583 2358 120 360 1 Urban Y
LP001008 Male No 0 Graduate No 6000 0 141 360 1 Urban Y

共614條數據記錄,每條記錄有十二個基本屬性,一個目標屬性(Loan_Status)。數據記錄可能包含缺失值。

數據集很小,直接導入Python環境即可:

import pandas as pd
import numpy as np

data = pd.read_csv(r"F:\Datahack_Loan_Prediction\Loan_Prediction_Train.csv", index_col="Loan_ID")

注意這里我們指定index_col為Loan_ID這一列,所以程序會把csv文件中Loan_ID這一列作為DataFrame的索引。默認情況下index_col為False,程序自動創建int型索引,從0開始。

1. 布爾索引(Boolean Indexing)

有時我們希望基于某些列的條件篩選出需要的記錄,這時可以使用loc索引的布爾索引功能。比方說下面的代碼篩選出全部無大學學歷但有貸款的女性列表:

data.loc[(data["Gender"]=="Female") & (data["Education"]=="Not Graduate") & (data["Loan_Status"]=="Y"), ["Gender","Education","Loan_Status"]]

loc索引是優先采用label定位的,label可以理解為索引。前面我們定義了Loan_ID為索引,所以對這個DataFrame使用loc定位時就可以用Loan_ID定位:

data.loc['LP001002']
Gender                   Male
Married                    No
Dependents                  0
Education            Graduate
Self_Employed              No
ApplicantIncome          5849
CoapplicantIncome           0
LoanAmount            129.937
Loan_Amount_Term          360
Credit_History              1
Property_Area           Urban
Loan_Status                 Y
LoanAmount_Bin         medium
Loan_Status_Coded           1
Name: LP001002, dtype: object

可以看到我們指定了一個Loan_ID,就定位到這個Loan_ID對應的記錄。

loc允許四種input方式:

  1. 指定一個label;

  2. label列表,比如['LP001002','LP001003','LP001004'];

  3. label切片,比如'LP001002':'LP001003';

  4. 布爾數組

我們希望基于列值進行篩選就用到了第4種input方式-布爾索引。使用()括起篩選條件,多個篩選條件之間使用邏輯運算符&,|,~與或非進行連接,特別注意,和我們平常使用Python不同,這里用and,or,not是行不通的。

此外,這四種input方式都支持第二個參數,使用一個columns的列表,表示只取出記錄中的某些列。上面的例子就是只取出了Gender,Education,Loan_Status這三列,當然,獲得的新DataFrame的索引依然是Loan_ID。

想了解更多請閱讀 Pandas Selecting and Indexing

2. Apply函數

Apply是一個方便我們處理數據的函數,可以把我們指定的一個函數應用到DataFrame的每一行或者每一列(使用參數axis設定,默認為0,即應用到每一列)。

如果要應用到特定的行和列只需要先提取出來再apply就可以了,比如data['Gender'].apply(func)。特別地,這里指定的函數可以是系統自帶的,也可以是我們定義的(可以用匿名函數)。比如下面這個例子統計每一列的缺失值個數:

# 創建一個新函數:

def num_missing(x):

  return sum(x.isnull())

# Apply到每一列:
print("Missing values per column:")

print(data.apply(num_missing, axis=0)) # axis=0代表函數應用于每一列
Missing values per column:
Gender               13
Married               3
Dependents           15
Education             0
Self_Employed        32
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount           22
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

下面這個例子統計每一行的缺失值個數,因為行數太多,所以使用head函數僅打印出DataFrame的前5行:

# Apply到每一行:
print("\nMissing values per row:")

print(data.apply(num_missing, axis=1).head()) # axis=1代表函數應用于每一行
Missing values per row:
Loan_ID
LP001002    1
LP001003    0
LP001005    0
LP001006    0
LP001008    0
dtype: int64

想了解更多請閱讀 Pandas Reference (apply)

3. 替換缺失值

一般來說我們會把某一列的缺失值替換為所在列的平均值/眾數/中位數。fillna()函數可以幫我們實現這個功能。但首先要從scipy庫導入獲取統計值的函數。

# 首先導入一個尋找眾數的函數:

from scipy.stats import mode

GenderMode = mode(data['Gender'].dropna())
print(GenderMode)
print(GenderMode.mode[0],':',GenderMode.count[0])
ModeResult(mode=array(['Male'], dtype=object), count=array([489]))
Male : 489

F:\Anaconda3\lib\site-packages\scipy\stats\stats.py:257: RuntimeWarning: The input array could not be properly checked for nan values. nan values will be ignored.
  "values. nan values will be ignored.", RuntimeWarning)

可以看到對DataFrame的某一列使用mode函數可以得到這一列的眾數以及它所出現的次數,由于眾數可能不止一個,所以眾數的結果是一個列表,對應地出現次數也是一個列表。可以使用.mode.count提取出這兩個列表。

特別留意,可能是版本原因,我使用的scipy (0.17.0)不支持原帖子中的代碼,直接使用mode(data['Gender'])是會報錯的:

F:\Anaconda3\lib\site-packages\scipy\stats\stats.py:257: RuntimeWarning: The input array could not be p
roperly checked for nan values. nan values will be ignored.
  "values. nan values will be ignored.", RuntimeWarning)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "F:\Anaconda3\lib\site-packages\scipy\stats\stats.py", line 644, in mode
    scores = np.unique(np.ravel(a))       # get ALL unique values
  File "F:\Anaconda3\lib\site-packages\numpy\lib\arraysetops.py", line 198, in unique
    ar.sort()
TypeError: unorderable types: str() > float()

必須使用mode(data['Gender'].dropna()),傳入dropna()處理后的DataFrame才可以。雖然Warning仍然存在,但是可以執行得到結果。Stackoverflow里有這個問題的討論:Scipy Stats Mode function gives Type Error unorderable types。指出scipy的mode函數無法處理列表中包含混合類型的情況,比方說上面的例子就是包含了缺失值NAN類型和字符串類型,所以無法直接處理。

同時也指出Pandas自帶的mode函數是可以處理混合類型的,我測試了一下:

from pandas import Series
Series.mode(data['Gender'])
0    Male
dtype: object

確實沒問題,不需要使用dropna()處理,但是只能獲得眾數,沒有對應的出現次數。返回結果是一個Pandas的Series對象。可以有多個眾數,索引是int類型,從0開始。

掌握了獲取眾數的方法后就可以使用fiilna替換缺失值了:

#值替換:
data['Gender'].fillna(GenderMode.mode[0], inplace=True)

MarriedMode = mode(data['Married'].dropna())
data['Married'].fillna(MarriedMode.mode[0], inplace=True)

Self_EmployedMode = mode(data['Self_Employed'].dropna())
data['Self_Employed'].fillna(Self_EmployedMode.mode[0], inplace=True)

先提取出某一列,然后用fillna把這一列的缺失值都替換為計算好的平均值/眾數/中位數。inplace關鍵字用于指定是否直接對這個對象進行修改,默認是False,如果指定為True則直接在對象上進行修改,其他地方調用這個對象時也會收到影響。這里我們希望修改直接覆蓋缺失值,所以指定為True。

#再次檢查缺失值以確認:
print(data.apply(num_missing, axis=0))
Gender                0
Married               0
Dependents           15
Education             0
Self_Employed         0
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount           22
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

可以看到Gender,Married,Self_Employed這幾列的缺失值已經都替換成功了,所以缺失值的個數為0。

想了解更多請閱讀 Pandas Reference (fillna)

4. 透視表

透視表(Pivot Table)是一種交互式的表,可以動態地改變它的版面布置,以便按照不同方式分析數據,也可以重新安排行號、列標、頁字段,比如下面的Excel透視表:

Excel透視表

可以自由選擇用來做行號和列標的屬性,非常便于我們做不同的分析。Pandas也提供類似的透視表的功能。例如LoanAmount這個重要的列有缺失值。我們不希望直接使用整體平均值來替換,這樣太過籠統,不合理。

這時可以用先根據 GenderMarriedSelf_Employed分組后,再按各組的均值來分別替換缺失值。每個組 LoanAmount的均值可以用如下方法確定:

#Determine pivot table

impute_grps = data.pivot_table(values=["LoanAmount"], index=["Gender","Married","Self_Employed"], aggfunc=np.mean)

print(impute_grps)
                              LoanAmount
Gender Married Self_Employed
Female No      No             114.691176
               Yes            125.800000
       Yes     No             134.222222
               Yes            282.250000
Male   No      No             129.936937
               Yes            180.588235
       Yes     No             153.882736
               Yes            169.395833

關鍵字values用于指定要使用集成函數(字段aggfunc)計算的列,選填,不進行指定時默認對所有index以外符合集成函數處理類型的列進行處理,比如這里使用mean作集成函數,則合適的類型就是數值類型。index是行標,可以是多重索引,處理時會按序分組。

想了解更多請閱讀 Pandas Reference (Pivot Table)

5. 多重索引

不同于DataFrame對象,可以看到上一步得到的Pivot Table每個索引都是由三個值組合而成的,這就叫做多重索引。

從上一步中我們得到了每個分組的平均值,接下來我們就可以用來替換LoanAmount字段的缺失值了:

#只在帶有缺失值的行中迭代:

for i,row in data.loc[data['LoanAmount'].isnull(),:].iterrows():

  ind = tuple([row['Gender'],row['Married'],row['Self_Employed']])

  data.loc[i,'LoanAmount'] = impute_grps.loc[ind].values[0]

特別注意對Pivot Table使用loc定位的方式,Pivot Table的索引是一個tuple。從上面的代碼可以看到,先把DataFrame中所有LoanAmount字段缺失的數據記錄取出,并且使用iterrows函數轉為一個按行迭代的對象,每次迭代返回一個索引(DataFrame里的索引)以及對應行的內容。然后從行的內容中把 GenderMarriedSelf_Employed三個字段的值提取出放入一個tuple里,這個tuple就可以用作前面定義的Pivot Table的索引了。

接下來的賦值對DataFrame使用loc定位根據索引定位,并且只抽取LoanAmount字段,把它賦值為在Pivot Table中對應分組的平均值。最后檢查一下,這樣我們又處理好一個列的缺失值了。

#再次檢查缺失值以確認:

print(data.apply(num_missing, axis=0))
Gender                0
Married               0
Dependents           15
Education             0
Self_Employed         0
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount            0
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

6. 二維表

二維表這個東西可以幫助我們快速驗證一些基本假設,從而獲取對數據表格的初始印象。例如,本例中Credit_History字段被認為對會否獲得貸款有顯著影響。可以用下面這個二維表進行驗證:

pd.crosstab(data["Credit_History"],data["Loan_Status"],margins=True)

crosstab的第一個參數是index,用于行的分組;第二個參數是columns,用于列的分組。代碼中還用到了一個margins參數,這個參數默認為False,啟用后得到的二維表會包含匯總數據。

然而,相比起絕對數值,百分比更有助于快速了解數據。我們可以用apply函數達到目的:

def percConvert(ser):
  return ser/float(ser[-1])

pd.crosstab(data["Credit_History"],data["Loan_Status"],margins=True).apply(percConvert, axis=1)

從這個二維表中我們可以看到有信用記錄 (Credit_History字段為1.0) 的人獲得貸款的可能性更高。接近80%有信用記錄的人都 獲得了貸款,而沒有信用記錄的人只有大約8% 獲得了貸款。令人驚訝的是,如果我們直接利用信用記錄進行訓練集的預測,在614條記錄中我們能準確預測出460條記錄 (不會獲得貸款+會獲得貸款:82+378) ,占總數足足75%。不過呢~在訓練集上效果好并不代表在測試集上效果也一樣好。有時即使提高0.001%的準確度也是相當困難的,這就是我們為什么需要建立模型進行預測的原因了。

想了解更多請閱讀 Pandas Reference (crosstab)

7. 數據框合并

就像數據庫有多個表的連接操作一樣,當數據來源不同時,會產生把不同表格合并的需求。這里假設不同的房產類型有不同的房屋均價數據,定義一個新的表格,如下:

prop_rates = pd.DataFrame([1000, 5000, 12000], index=['Rural','Semiurban','Urban'],columns=['rates'])

prop_rates

農村房產均價只用1000,城郊這要5000,城鎮內房產比較貴,均價為12000。我們獲取到這個數據之后,希望把它連接到原始表格Loan_Prediction_Train.csv中以便觀察房屋均價對預測的影響。在原始表格中有一列Property_Area就是表明貸款人居住的區域的,可以通過這一列進行表格連接:

data_merged = data.merge(right=prop_rates, how='inner',left_on='Property_Area',right_index=True, sort=False)

data_merged.pivot_table(values='Credit_History',index=['Property_Area','rates'], aggfunc=len)
Property_Area  rates
Rural          1000     179.0
Semiurban      5000     233.0
Urban          12000    202.0
Name: Credit_History, dtype: float64

使用merge函數進行連接,解析一下各個參數:

  • 參數right即連接操作右端表格;

  • 參數how指示連接方式,默認是inner,即內連接。可選left、right、outer、inner;

    • left: use only keys from left frame (SQL: left outer join)
    • right: use only keys from right frame (SQL: right outer join)
    • outer: use union of keys from both frames (SQL: full outer join)
    • inner: use intersection of keys from both frames (SQL: inner join)
  • 參數left_on用于指定連接的key的列名,即使key在兩個表格中的列名不同,也可以通過left_on和right_on參數分別指定。 如果一樣的話,使用on參數就可以了。可以是一個標簽(單列),也可以是一個列表(多列);

  • right_index默認為False,設置為True時會把連接操作右端表格的索引作為連接的key。同理還有left_index;

  • sort參數默認為False,指示是否需要按key排序。

所以上面的代碼是把data表格和prop_rates表格連接起來。連接時,data表格用于連接的key是Property_Area,而prop_rates表格用于連接的key是索引,它們的值域是相同的。

連接之后使用了第四小節透視表的方法檢驗新表格中Property_Area字段和rates字段的關系。后面跟著的數字表示出現的次數。

想了解更多請閱讀 Pandas Reference (merge)

8. 給數據排序

Pandas可以輕松地基于多列進行排序,方法如下:

data_sorted = data.sort_values(['ApplicantIncome','CoapplicantIncome'], ascending=False)

data_sorted[['ApplicantIncome','CoapplicantIncome']].head(10)

Notice:Pandas 的sort函數現在已經不推薦使用,使用 sort_values函數代替。

這里傳入了ApplicantIncomeCoapplicantIncome兩個字段用于排序,Pandas會先按序進行。先根據ApplicantIncome進行排序,對于ApplicantIncome相同的記錄再根據CoapplicantIncome進行排序。 ascending參數設為False,表示降序排列。不妨再看個簡單的例子:

from pandas import DataFrame
df_temp = DataFrame({'a':[1,2,3,4,3,2,1],'b':[0,1,1,0,1,0,1]})
df_sorted = df_temp.sort_values(['a','b'],ascending=False)
df_sorted

這里只有a和b兩列,可以清晰地看到Pandas的多列排序是先按a列進行排序,a列的值相同則會再按b列的值排序。

想了解更多請閱讀 Pandas Reference (sort_values)

9. 繪圖(箱型圖&直方圖)

Pandas除了表格操作之外,還可以直接繪制箱型圖和直方圖且只需一行代碼。這樣就不必單獨調用matplotlib了。

%matplotlib inline
# coding:utf-8
data.boxplot(column="ApplicantIncome",by="Loan_Status")
箱型圖1

箱型圖

因為之前沒怎么接觸過箱型圖,所以這里單獨開一節簡單歸納一下。

箱形圖(英文:Box-plot),又稱為盒須圖、盒式圖、盒狀圖或箱線圖,是一種用作顯示一組數據分散情況資料的統計圖。因型狀如箱子而得名。詳細解析看維基百科

因為上面那幅圖不太容易看,用個簡單點的例子來說,還是上一小節那個只有a列和b列的表格。按b列對a列進行分組:

df_temp.boxplot(column="a",by="b")
箱型圖2

定義b列值為0的分組為Group1,b列值為1的分組為Group2。Group1分組有4,2,1三個值,毫無疑問最大值4,最小值1,在箱型圖中這兩個值對應箱子發出的虛線頂端的兩條實線。Group2分組有3,3,2,1四個值,由于最大值3和上四分位數3(箱子頂部)相同,所以重合了。

Group1中位數是2,而Group2的中位數則是中間兩個數2和3的平均數,也即2.5。在箱型圖中由箱子中間的有色線段表示。

四分位數

四分位數是統計學的一個概念。把所有數值由小到大排列好,然后分成四等份,處于三個分割點位置的數值就是四分位數,其中:

  • 第一四分位數 (Q1),又稱“較小四分位數”或“下四分位數”,等于該樣本中所有數值由小到大排列后,在四分之一位置的數。
  • 第二四分位數 (Q2),又稱“中位數”,等于該樣本中所有數值由小到大排列后,在二分之一位置的數。
  • 第三四分位數 (Q3),又稱“較大四分位數”或“上四分位數”,等于該樣本中所有數值由小到大排列后,在四分之三位置的數。

Notice:Q3與Q1的差距又稱四分位距(InterQuartile Range, IQR)。

計算四分位數時首先計算位置,假設有n個數字,則:

  • Q1位置 = (n-1) / 4
  • Q2位置 = 2 * (n-1) / 4 = (n-1) / 2
  • Q3位置 = 3 * (n-1) / 4

如果n-1恰好是4的倍數,那么數列中對應位置的就是各個四分位數了。但是,如果n-1不是4的倍數呢

這時位置會是一個帶小數部分的數值,四分位數以距離該值最近的兩個位置的加權平均值求出。其中,距離較近的數,權值為小數部分;而距離較遠的數,權值為(1-小數部分)。

再看例子中的Group1,Q1位置為0.5,Q2位置為1,Q3位置為1.5。(注意:位置從下標0開始!),所以:

Q1 = 0.5*1+0.5*2 = 1.5
Q2 = 2
Q3 = 0.5*2+0.5*4 = 3

而Group2中,Q1位置為0.75,Q2位置為1.5,Q3位置為2.25。

Q1 = 0.25*1+0.72*2 = 1.75
Q2 = 0.5*2+0.5*3 = 2.5
Q3 = 0.25*3+0.75*3 = 3

這樣是否就清晰多了XD 然而,四分位數的取法還存在分歧,定義不一,我在學習這篇文章時也曾經很迷茫,直到閱讀了源碼!!

因為Pandas庫依賴numpy庫,所以它計算四分位數的方式自然也是使用了numpy庫的。而numpy中實現計算百分比數的函數為percentile,代碼實現如下:

def percentile(N, percent, key=lambda x:x):
    """
    Find the percentile of a list of values.

    @parameter N - is a list of values. Note N MUST BE already sorted.
    @parameter percent - a float value from 0.0 to 1.0.
    @parameter key - optional key function to compute value from each element of N.

    @return - the percentile of the values
    """
    if not N:
        return None
    k = (len(N)-1) * percent
    f = math.floor(k)
    c = math.ceil(k)
    if f == c:
        return key(N[int(k)])
    d0 = key(N[int(f)]) * (c-k)
    d1 = key(N[int(c)]) * (k-f)
    return d0+d1

讀一遍源碼之后就更加清晰了。最后舉個例子:

from numpy import percentile, mean, median
temp = [1,2,4] # 數列包含3個數
print(min(temp))
print(percentile(temp,25))
print(percentile(temp,50))
print(percentile(temp,75))
print(max(temp))

1
1.5
2.0
3.0
4

temp2 = [1,2,3,3] # 數列包含4個數
print(min(temp2))
print(percentile(temp2,25))
print(percentile(temp2,50))
print(percentile(temp2,75))
print(max(temp2))

1
1.75
2.5
3.0
3

temp3 = [1,2,3,4,5] # 數列包含5個數
print(min(temp2))
print(percentile(temp3,25))
print(percentile(temp3,50))
print(percentile(temp3,75))
print(max(temp3))

1
2.0
3.0
4.0
5

不熟悉的話就再手擼一遍!!!不要怕麻煩!!!這一小節到此Over~

直方圖

data.hist(column="ApplicantIncome",by="Loan_Status",bins=30)
直方圖

直方圖比箱型圖熟悉一些,這里就不詳細展開了。結合箱型圖和直方圖,我們可以看出獲得貸款的人和未獲得貸款的人沒有明顯的收入差異,也即收入不是決定性因素。

離群點

特別地,從直方圖上我們可以看出這個數據集在收入字段上,比較集中于一個區間,區間外部有些散落的點,這些點我們稱為離群點(Outlier)。前面將箱型圖時沒有細說,現在再回顧一下箱型圖:

箱型圖1

可以看到除了箱子以及最大最小值之外,還有很多橫線,這些橫線其實就表示離群點。那么可能又會有新的疑問了?怎么會有離群點在最大最小值外面呢?這樣豈不是存在比最大值大,比最小值小的情況了嗎?

其實之前提到一下,有一個概念叫四分位距(IQR),數值上等于Q3-Q1,記作ΔQ。定義:

  • 最大值區間: Q3+1.5ΔQ
  • 最小值區間: Q1-1.5ΔQ

也就是說最大值必須出現在這兩個區間內,區間外的值被視為離群點,并顯示在圖上。這樣做我們可以避免被過分偏離的數據點帶偏,更準確地觀測到數據的真實狀況,或者說普遍狀況。

想了解更多請閱讀 Pandas Reference (hist) | Pandas Reference (boxplot)

10. 用Cut函數分箱

有時把數值聚集在一起更有意義。例如,如果我們要為交通狀況(路上的汽車數量)根據時間(分鐘數據)建模。具體的分鐘可能不重要,而時段如“上午”“下午”“傍晚”“夜間”“深夜”更有利于預測。如此建模更直觀,也能避免過度擬合。
這里我們定義一個簡單的、可復用的函數,輕松為任意變量分箱。

#分箱:

def binning(col, cut_points, labels=None):

  #Define min and max values:

  minval = col.min()

  maxval = col.max()

  #利用最大值和最小值創建分箱點的列表

  break_points = [minval] + cut_points + [maxval]

  #如果沒有標簽,則使用默認標簽0 ... (n-1)

  if not labels:

    labels = range(len(cut_points)+1)

  #使用pandas的cut功能分箱

  colBin = pd.cut(col,bins=break_points,labels=labels,include_lowest=True)

  return colBin



#為年齡分箱:

cut_points = [90,140,190]

labels = ["low","medium","high","very high"]

data["LoanAmount_Bin"] = binning(data["LoanAmount"], cut_points, labels)

print(pd.value_counts(data["LoanAmount_Bin"], sort=False))


low          104
medium       273
high         146
very high     91
dtype: int64

解析以下這段代碼,首先定義了一個cut_points列表,里面的三個點用于把LoanAmount字段分割成四個段,對應地,定義了labels列表,四個箱子(段)按貸款金額分別為低、中、高、非常高。然后把用于分箱的LoanAmount字段,cut_points,labels傳入定義好的binning函數。

binning函數中,首先拿到分箱字段的最小值和最大值,把這兩個點加入到break_points列表的一頭一尾,這樣用于切割的所有端點就準備好了。如果labels沒有定義就默認按0~段數-1命名箱子。 最后借助pandas提供的cut函數進行分箱。

cut函數原型是cut(x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False),x是分箱字段,bins是區間端點,right指示區間右端是否閉合,labels不解釋..retbins表示是否需要返回bins,precision是label存儲和顯示的精度,include_lowest指示第一個區間的左端是否閉合。

在把返回的列加入到data后,使用value_counts函數,統計了該列的各個離散值出現的次數,并且指定不需要對結果進行排序。

想了解更多請閱讀 Pandas Reference (cut)

11.為分類變量編碼

有時,我們會面對要改動分類變量的情況。原因可能是:

有些算法(如logistic回歸)要求所有輸入項目是數字形式。所以分類變量常被編碼為0, 1….(n-1)
有時同一個分類變量可能會有兩種表現方式。如,溫度可能被標記為“High”, “Medium”, “Low”,“H”, “low”。這里 “High” 和 “H”都代表同一類別。同理, “Low” 和“low”也是同一類別。但Python會把它們當作不同的類別。
一些類別的頻數非常低,把它們歸為一類是個好主意

這里我們定義了一個函數,以字典的方式輸入數值,用‘replace’函數進行編碼

#使用Pandas replace函數定義新函數:

def coding(col, codeDict):

  colCoded = pd.Series(col, copy=True)

  for key, value in codeDict.items():

    colCoded.replace(key, value, inplace=True)

  return colCoded


#把貸款狀態LoanStatus編碼為Y=1, N=0:

print('Before Coding:')

print(pd.value_counts(data["Loan_Status"]))

data["Loan_Status_Coded"] = coding(data["Loan_Status"], {'N':0,'Y':1})

print('\nAfter Coding:')

print(pd.value_counts(data["Loan_Status_Coded"]))


Before Coding:
Y    422
N    192
Name: Loan_Status, dtype: int64

After Coding:
1    422
0    192
Name: Loan_Status_Coded, dtype: int64

在coding函數中第二個參數是一個dict,定義了需要編碼的字段中每個值對應的編碼。
實現上首先把傳入的列轉換為Series對象,copy參數表示是否要進行復制,關于copy可以看我另一篇筆記:深拷貝與淺拷貝的區別

copy得到的Series對象不會影響到原本DataFrame傳入的列,所以可以放心修改,這里replace函數中的inplace參數表示是否直接在調用對象上作出更改,我們選擇是,那么colCoded對像在調用replace后就會被修改為編碼形式了。最后,把編碼后的列加入到data中,比較編碼前后的效果。

想了解更多請閱讀 Pandas Reference (replace)

12. 在一個數據框的各行循環迭代

有時我們會需要用一個for循環來處理每行。比方說下面兩種情況:

  1. 帶數字的分類變量被當做數值。
  2. 帶文字的數值變量被當做分類變量。

先看看data表格的數據類型:

#檢查當前數據類型:

data.dtypes

結果:

Gender                 object
Married                object
Dependents             object
Education              object
Self_Employed          object
ApplicantIncome         int64
CoapplicantIncome     float64
LoanAmount            float64
Loan_Amount_Term      float64
Credit_History        float64
Property_Area          object
Loan_Status            object
LoanAmount_Bin       category
Loan_Status_Coded       int64
dtype: object

可以看到Credit_History這一列被當作浮點數,而實際上我們原意是分類變量。所以通常來說手動定義變量類型是個好主意。那這種情況下該怎么辦呢?這時我們需要逐行迭代了。

首先創建一個包含變量名和類型的csv文件,讀取該文件:

#載入文件:
colTypes = pd.read_csv(r'F:\Datahack_Loan_Prediction\datatypes.csv')
print(colTypes)
              feature         type
0             Loan_ID  categorical
1              Gender  categorical
2             Married  categorical
3          Dependents  categorical
4           Education  categorical
5       Self_Employed  categorical
6     ApplicantIncome   continuous
7   CoapplicantIncome   continuous
8          LoanAmount   continuous
9    Loan_Amount_Term   continuous
10     Credit_History  categorical
11      Property_Area  categorical
12        Loan_Status  categorical

載入這個文件之后,我們能對它的逐行迭代,然后使用astype函數來設置表格的類型。這里把離散值字段都設置為categorical類型(對應np.object),連續值字段都設置為continuous類型(對應np.float)。

#迭代每行,指派變量類型。

#注,astype函數用于指定變量類型。

for i, row in colTypes.iterrows(): #i: dataframe索引; row: 連續的每行

    if row['type']=="categorical" and row['feature']!="Loan_ID":
        data[row['feature']]=data[row['feature']].astype(np.object)
    elif row['type']=="continuous":
        data[row['feature']]=data[row['feature']].astype(np.float)

print(data.dtypes)
Gender                object
Married               object
Dependents            object
Education             object
Self_Employed         object
ApplicantIncome      float64
CoapplicantIncome    float64
LoanAmount           float64
Loan_Amount_Term     float64
Credit_History        object
Property_Area         object
Loan_Status           object
dtype: object

可以看到現在信用記錄(Credit_History)這一列的類型已經變成了‘object’ ,這在Pandas中代表分類變量。

特別地,無論是中文教程還是原版英文教程這個地方都出錯了.. 中文教材代碼中判斷條件是錯的,英文教程中沒有考慮到Loan_ID這個字段,由于它被設定為表格的索引,所以它的類型是不被考慮的。

想了解更多請閱讀 Pandas Reference (iterrows)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容