
一、cat對象

cat對象屬性
- 類別本身,通過Index類型存儲
- 是否有序, 通過的cat屬性訪問
s.cat.categories
s.cat.ordered
s.cat.codes #編號
類別的增刪改
s = s.cat.add_categories('Graduate') # 增加一個畢業(yè)生類別
s = s.cat.remove_categories('Freshman')
s = s.cat.set_categories(['Sophomore','PhD']) # 新類別為大二學生
和博士
s = s.cat.remove_unused_categories() # 移除了未出現(xiàn)的博士生類別
s = s.cat.rename_categories({'Sophomore':'本科二年級學生'})
二、有序分類

序的建立
- 通過
s.cat.as_ordered()可以將類別轉化為有序,有序類別和無序類別可以通過as_unordered和reorder_categories互相轉化
s = df.Grade.astype('category')
s = s.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
s.head()
s.cat.as_unordered().head()
排序和比較
- 有序的類別可以使用
sort_index和sort_values進行排序 - 也可以是使用比較運算符進行比較,主義在使用大小比較時比較的對象必須存在category中,不然無法比較
三、區(qū)間類別

cut和qcut
-
cut:可以指定分割區(qū)間的數(shù)量或者通過list指定端點值
pd.cut(s, bins=2, right=False)
pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
-
qcut:可以指定分位等分的數(shù)量或者通過list指定端點值(分位數(shù))
pd.qcut(s, q=3)
pd.qcut(s, q=[0,0.2,0.8,1])
二者皆可使用labels指定區(qū)間名稱
區(qū)間的構造
- 通過pd.Interval構造, 指定左右端點和閉合開閉狀態(tài)
my_interval = pd.Interval(0, 1, 'right')
- 通過pd.IntervalIndex構造
- 從cut或者qcut的結果轉換
- from_breaks
- from_arrays
- from_tuples
- interval_range
id_interval = pd.IntervalIndex(pd.cut(s, 3))
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
pd.interval_range(start=1,end=5,periods=8)
【練一練】
無論是interval_range還是下一章時間序列中的date_range都是給定了等差序列中四要素中的三個,從而確定整個序列。請回顧等差數(shù)列中的首項、末項、項數(shù)和公差的聯(lián)系,寫出interval_range中四個參數(shù)之間的恒等關系。
(end - start) / freq == periodes
區(qū)間的屬性與方法
- overlaps:判斷是否有交集
id_demo.overlaps(pd.Interval(40,60))
- contains: 判斷區(qū)間是否含有某個元素
id_demo.contains(4)
- 屬性:
- left
- right
- mid
- length
id_demo.left
id_demo.right
id_demo.mid
id_demo.length
四、練習
Ex1:統(tǒng)計未出現(xiàn)的類別
在第五章中介紹了crosstab函數(shù),在默認參數(shù)下它能夠對兩個列的組合出現(xiàn)的頻數(shù)進行統(tǒng)計匯總:
df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
pd.crosstab(df.A, df.B)
但事實上有些列存儲的是分類變量,列中并不一定包含所有的類別,此時如果想要對這些未出現(xiàn)的類別在crosstab結果中也進行匯總,則可以指定dropna參數(shù)為False:
請實現(xiàn)一個帶有dropna參數(shù)的my_crosstab函數(shù)來完成上面的功能。
構造s1與s2的dataframe, 將s1.name作為index,索引出相關的行,然后使用
==計算與s2.name相等的元素的個數(shù)
def my_crosstab(s1, s2, dropna=True):
columns = s2.cat.categories[s2.cat.categories.isin(s2)]
table = pd.concat([s1,s2], axis=1).set_index(s1.name)
if dropna:
_columns = columns
else:
_columns = s2.cat.categories
ret = pd.DataFrame(index=index, columns=_columns, data=np.zeros((len(index), len(_columns))))
res = res.rename_axis(index=s1.name, columns=s2.name).astype('int')
for idx in index:
content = table.loc[idx]
for c in columns:
ret.loc[idx, c] = (content == c).values.sum()
return ret
my_crosstab(s1, s2, dropna=False)

Ex2:鉆石數(shù)據(jù)集
現(xiàn)有一份關于鉆石的數(shù)據(jù)集,其中carat, cut, clarity, price分別表示克拉重量、切割質量、純凈度和價格,樣例如下:
df = pd.read_csv('../data/diamonds.csv')
df.head(3)
- 分別對
df.cut在object類型和category類型下使用nunique函數(shù),并比較它們的性能。 - 鉆石的切割質量可以分為五個等級,由次到好分別是
Fair, Good, Very Good, Premium, Ideal,純凈度有八個等級,由次到好分別是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,請對切割質量按照由好到次的順序排序,相同切割質量的鉆石,按照純凈度進行由次到好的排序。 - 分別采用兩種不同的方法,把
cut, clarity這兩列按照由好到次的順序,映射到從0到n-1的整數(shù),其中n表示類別的個數(shù)。 - 對每克拉的價格按照分別按照分位數(shù)(q=[0.2, 0.4, 0.6, 0.8])與[1000, 3500, 5500, 18000]割點進行分箱得到五個類別
Very Low, Low, Mid, High, Very High,并把按這兩種分箱方法得到的category序列依次添加到原表中。 - 第4問中按照整數(shù)分箱得到的序列中,是否出現(xiàn)了所有的類別?如果存在沒有出現(xiàn)的類別請把該類別刪除。
- 對第4問中按照分位數(shù)分箱得到的序列,求每個樣本對應所在區(qū)間的左右端點值和長度。
根據(jù)結果可知, category類型速度略快
# 性能測量
%timeit -n 100 df.cut.nunique()
cat = df.cut.astype('category')
%timeit -n 100 cat.nunique()

轉換為category類型后使用reorder_categories轉換成有序類型排序即可
df.cut = df.cut.astype('category').cat.reorder_categories(['Fair', 'Good', 'Very Good', 'Premium', 'Ideal'])
df.clarity = df.clarity.astype('category').cat.reorder_categories(['I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'])
df.sort_values(['cut', 'clarity'], ascending=[False, True]).head()

利用cat.code或者replace
df.cut = df.cut.cat.reorder_categories(df.cut.cat.categories[::-1])
df.clarity = df.clarity.cat.reorder_categories(df.clarity.cat.categories[::-1])
df.cut = df.cut.cat.codes # 方法一:利用cat.codes
clarity_cat = df.clarity.cat.categories
df.clarity = df.clarity.replace(dict(zip(clarity_cat, np.arange(len(clarity_cat))))) # 方法二:使用replace映射
使用qcut和cut,注意的是對區(qū)間進行補全,使其正確地分為5個區(qū)間
pricePerCarat = df.price / df.carat
type1 = pd.qcut(pricePerCarat, q=[0, 0.2, 0.4, 0.6, 0.8, 1], labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])
type2 = pd.cut(pricePerCarat, bins=[0, 1000, 3500, 5500, 18000, np.inf], labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])
type1.name = 'type1'
type2.name = 'type2'
df = pd.concat([df, type1, type2], axis=1)
df.head()

通過唯一值的數(shù)目判斷所有的種類是否都出現(xiàn)了,可以知道使用cut劃分區(qū)間的種類中少了
Very Low和Very High,使用remove_categories移除不存在的類別即可
print(df.type1.cat.categories.nunique() == df.type1.nunique())
print(df.type2.cat.categories.nunique() == df.type2.nunique())
cond = df.type2.cat.categories.isin(df.type2)
df.type2.cat.remove_categories(df.type2.cat.categories[~cond])

使用pd.IntervalIndex將分區(qū)結果轉換成區(qū)間之后調用相關屬性即可
interval = pd.IntervalIndex(pd.qcut(pricePerCarat, q=[0, 0.2, 0.4, 0.6, 0.8, 1]))
interval.right #右端點
interval.left #左端點
interval.length #區(qū)間長度


