給定數據文件data.csv,其中記錄的是用戶用電數據。數據中有編號為1~200的200位用戶,DATA_DATE表示時間,如:2015/1/1表示2015年1月1日, KWH表示用電量。
文件下載:
鏈接:https://pan.baidu.com/s/15PvDmfDqgdNM1hXAa59L8A
提取碼:a245
請用給定的數據,實現以下任務:
數據轉置
將數據進行轉置,轉置后型如eg.csv, 缺失值用NAN代替。
分析過程:
轉置的話想到了numpy的T,但是他的話是直接轉的,和所給的eg.csv格式不相同而且轉了之后對數據處理沒有任何用處。給的例子是以CONS_NO為索引,DATA_DATE為字段的,腦子里沒啥想法,於是百度
pandas的轉置,pandas的透視表,我感覺寫的很詳細了。
找到了pivot_table()函數,即透視表,一種可以對數據動態排布並且分類匯總的表格格式。用pd.pivot_table(data, index='CONS_NO', columns='DATA_DATE')進行轉置即可。
在excel里面也是有這個功能的。
缺失值的識別和替換,用isnull()判斷是否為空值,sum()函數對空值進行統計,替換的話用fillna(),功能是填充缺失值,這里要注意不要直接用字符串‘NAN’,要用np.NAN,不然數據類型會不同,很難處理。
讀cvs的時候,需要將DATA_DATE以時間類型讀入。
還有就是空值和缺失值的區別也要注意:空值在pandas中的空值是"";而缺失值在dataframe中為nan或者naT(缺失時間),在series中為none或者nan。
代碼:
import pandas as pd
import numpy as np
#(1)數據轉置,缺失值用NAN代替。
data = pd.read_csv('data.csv', parse_dates=[1])
null_value = data.isna().sum() # 缺失值識別
print("data具有的缺失值:\n",null_value)
data = data.fillna(value=np.NAN) # 不要直接用字符串‘NAN’否則數據類型會不同。
result = pd.pivot_table(data, index='CONS_NO', columns='DATA_DATE')
result.to_csv('res.csv')
實驗結果:
異常值識別
對數據中的異常值進行識別並用NA代替。
分析過程:
異常值和缺失值是不同的概念,要搞清楚。異常值是指數據中個別值的數值明顯偏離其余的數值,有時也成為離群點,檢測異常值就是檢驗數據中是否有錄入錯誤以及是否含有不合理的數據。怎么檢測呢,繼續百度。
異常值的檢測常用的方法有:業務法、3σ原則和箱線圖分析。
我采用3σ原則檢測異常值:如果數據服從正態分布,異常值被定義為一組測定值中與平均值的偏差超過3倍的值 → p(|x - μ| > 3σ) ≤ 0.003。
異常數據常用處理方法:
1.刪除法(前提是異常觀測的比例不能太大)
2.替換法(可以考慮使用低於判別上下限的最大值或最小值,均值或中位數替換等)
題目是用NA代替。
代碼:
# (2)異常值識別/代替
u = data['KWH'].mean() # 平均值
si = data['KWH'].std() # 標准差
three_si= data['KWH'].apply(lambda x: x>u+3*si or x<u-3*si )
# print(three_si)
result1 = data.loc[three_si,'KWH'] # 使用3σ方法識別異常值
print("data在3σ下具有的異常值(前10):\n",result1.head(10))
data.loc[three_si,'KWH'] = pd.NA
print("異常值替換成NA(后10):\n",data.loc[three_si,'KWH'].tail(10))
實驗結果:
每個用戶用電數據的基本統計量
計算每個用戶用電數據的基本統計量,包括:最大值、最小值、均值、中位數、和、方差、偏度、峰度。(不包括空值)
分析過程:
主要是幾個函數的運用,具體如下:
| 功能 |函數 |功能 |函數|
|--|--|--|--|--|
| 最大值 | df.max() | 和 | df.sum() |
| 最小值 | df.min() | 方差 | df.var() |
| 均值 | df.mean() | 偏度 | df.skew() |
| 中位數 | df.median() | 峰度 | df.kurt() |
因為展示出來的格式對的不太齊,看着很難受,於是也百度了下對表格輸出的格式化展示。
代碼:
# (3)求每個用戶的統計量
def statistics(df): # 數據統計並合並統計量
statistical_table = pd.concat([df.max(), df.min(), df.mean(), df.median(), df.sum(), df.var(), df.skew(), df.kurt()],axis=1)
statistical_table.columns = ['最大值','最小值','均值','中位數','和','方差','偏度','峰度']
return statistical_table
# 對其輸出
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
pd.set_option('display.width',180) # 設置打印寬度(**重要**)
print("每個用戶用電數據的基本統計量:\n",statistics(result.T))
實驗結果:
用電數據按日差分
計算每個用戶用電數據按日差分,並計算差分結果的基本統計量,統計量同上述第3問。
分析過程:
差分(difference methods):統計學中的差分,就是指離散函數的后一項減去前一項的差。這里可以使用diff方法進行。
diff的基本使用方法:
函數 | 功能 |
---|---|
df.diff() | 縱向一階差分,當前行減去上一行 |
df.diff(axis=1) | 橫向一階差分,當前列減去左邊的列 |
df.diff(periods=2) | 縱向二階差分 |
df.diff(periods=2).dropna() | 縱向二階差分,丟棄空值 |
這里對result進行橫向一階差分,再調用statistics,對按日差分的數據進行統計,並輸出所有的基本統計量。
代碼:
# (4)每個用戶用電數據按日差分,差分結果的基本統計量
difference = result.diff(axis=1) # 橫向一階差分
print("每個用戶用電數據按日差分:\n",difference.T)
print("每個用戶用電數據按日差分的基本統計量:\n",statistics(difference.T))
實驗結果:
用電數據的5%分位數
計算每個用戶用電數據的5%分位數。
分析過程:
運用df.quantile()函數求分位數。
代碼:
# (5)用電數據的5%分位數。
print("每個用戶的5%分位數為:\n",result.quantile(0.05,axis=1))
實驗結果:
用電數據按周求和並差分
對每個用戶的用電數據按周求和並差分(一周7天),計算差分結果的基本統計量,統計量同第3問。
分析過程:
首先要把每周分開,這里使用pd.PeriodIndex重組時間序列 :主要將數據中的分離的時間字段,重組為時間序列,並指定為index。DATA_DATE按周的頻率排列,使用rep='w',同時獲取其索引。
接着按照CONS_NO及new_index進行分組,並求和。創建透視表,差分,最后使用statistics統計基本統計量。
代碼:
# (6) 每個用戶數據按周求和並差分(一周7天),差分結果的基本統計量
w_index = pd.PeriodIndex(data['DATA_DATE'], freq='w')
sum_week = data.groupby(by=['CONS_NO', w_index]).sum()
week_table = pd.pivot_table(sum_week, index='DATA_DATE', columns='CONS_NO')
# print(week_table)
diff_week = week_table.diff(1)
print("每個用戶按周求和並差分:\n",diff_week)
print("每個用戶按周求和並差分的基本統計量:\n",statistics(diff_week))
實驗結果:
用電數大於‘0.9最大值’的天數
每個用戶在一段時間內會有用電數據最大值,統計用電數大於‘0.9最大值’的天數。
分析過程:
apply方法。df.apply(func, axis=0) :將函數func 應用到DataFrame對象df的行或列所構成的一維數組上。
代碼:
# (7) 每個用戶用電數大於‘0.9最大值’的天數
max_d = result.apply(lambda x:x>x.max()*0.9,axis=1).sum(axis=1)
print("每個用戶用電數大於‘0.9最大值’的天數:\n",max_d)
實驗結果:
用電數據出現最大值和最小值的月份分析
獲取每個用戶用電數據出現最大值和最小值的月份,若最大值(最小值)存在於多個月份,則輸出含有最大值(最小值)最多的那個月份。我們按天統計每個用戶的用電數據,假設1號用戶用電量的最小值為0(可能是當天外出沒有用電),在一年的12個月,每個月都有可能有若干天用電量為0,那么就輸出含有最多用電量為0的天數所在的月份。最大用電量統計同理。
分析過程:
嘗試了好多次,本來是想要按月分組,然后找出每個人的最小值,然后統計最小值的月出現最小值的個數,取最大個數得出結論。但是嘗試了好4、5個小時,一直失敗,什么key有問題啊各種奇奇怪怪的報錯,真的要吐了。於是借鑒了網上的方法,我真是菜雞。
對於最小值,每月基本都有0,所以直接先求出整個data數據中最小值的索引,獲得的索引使用iloc進行切片,再使用PeriodIndex方法和groupby使得DATA_DATE以月為單位進行分組。接着使用count()函數計算出每月出現最小值的次數(因為篩選后的數據只有最小值了,所以可以用count)。接下來使用reset_index與idxmax方法獲取含有最小值最多的那個月份的索引,最用將獲得的索引使用iloc進行切片,得到所需結果。
最大值也是同理,但是沒有最小值那么復雜,不過個人覺得代碼似乎有些小問題。
代碼:
# (8)每個用戶用電數據出現最大值和最小值最多的月份
print("用戶用電數據出現最小值最多的月份:")
min_index = data[(data.KWH == data.KWH.min())].index # 最小值的索引
min_temp = data.iloc[min_index]
m_index = pd.PeriodIndex(min_temp['DATA_DATE'], freq='m')
min_count_df = pd.DataFrame(min_temp.groupby(['CONS_NO', m_index])['KWH'].count())# 按月進行分組,求最小值個數
min_count_df.columns = ['KWH最小值的次數']
min_count_df_index = min_count_df.reset_index().groupby('CONS_NO')['KWH最小值的次數'].idxmax()# 出現最小值次數最多的月份索引
min_value = min_count_df.iloc[min_count_df_index]
print(min_value) # 輸出含有最小值最多的那個月份
print("用戶用電數據出現最大值最多的月份:")
m_index = pd.PeriodIndex(data['DATA_DATE'], freq='m')
mon_df =pd.DataFrame(data.groupby(['CONS_NO',m_index])['KWH'].max())# 按月進行分組,並求
max_index = mon_df.reset_index().groupby('CONS_NO')['KWH'].idxmax()
max_value = mon_df.iloc[max_index]
max_value.columns = ['各用戶的KWH最大值']
print(max_value) # 輸出含有最大值最多的那個月份
實驗結果:
7月和8月用電數據與3月和4月用電數據分析
以每個用戶7月和8月用電數據為同一批統計樣本,3月和4月用電數據為另一批統計樣本,分別計算這兩批樣本之間的總體和(sum)之比,均值(mean)之比,最大值(max)之比和最小值(min)之比。
分析過程:
因為要統計7、8月和3、4月,所以要按月划分,所以先用一次PeriodIndex和groupby,對於分組后的數據先做第一次分析,及求每月的最大值、最小值、均值、和;接着篩選出7、8月和3、4月的數據,這里用idx = pd.IndexSlice 層級索引創建對象以更輕松地執行多索引切片,得到兩個表。接着以'CONS_NO'進行分組,對相關數據進行第二次分析,即所篩選出來的這幾個月的最大值、最小值、均值、和,兩次分析后得到78月和34月這幾個相關值的數據,再寫一個函數計算這兩個表每列之間的比值,這里要把每列提出來,然后計算,最后將結果放在一個總結表里即可。
代碼:
# (9)7、8月和3、4月相關比值
def date_filter(df): # 日期篩選,返回兩張表
idx = pd.IndexSlice
mon78 = df.loc[idx[:,['2015-7','2015-8', '2016-7', '2016-8']],:]
mon34 = df.loc[idx[:,['2015-3','2015-4', '2016-3', '2016-4']],:]
return mon78, mon34
def date_merge(df_1, df_2, name): # 合並符合要求的日期,同時進行比值處理
df_ratio = pd.merge(df_1, df_2, on='CONS_NO')
df_ratio.columns = ['7-8月', '3-4月']
df_ratio[name] = df_ratio['7-8月'] / df_ratio['3-4月']
return df_ratio
def analysis(df): # 每月數據統計
a_table = pd.concat([df.max()['KWH'], df.min()['KWH'], df.mean(), df.sum()],axis=1)
a_table.columns = ['最大值','最小值','均值','和']
return a_table
def analysis2(df):# 篩選出來的數據統計
a2_table = pd.concat([df['最大值'].max(),df['最小值'].min(),df['均值'].mean(),df['和'].sum()],axis=1)
return a2_table
m_index = pd.PeriodIndex(data['DATA_DATE'], freq='m')
m_all = data.groupby(['CONS_NO', m_index])# 按月進行分組
mon78,mon34 = date_filter(analysis(m_all))
mon_78 = analysis2(mon78.groupby('CONS_NO'))
mon_34 = analysis2(mon34.groupby('CONS_NO'))
col = ['最大值','最小值','均值','和']
names=['max_ratio','min_ratio','mean_ratio','sum_ratio']
summary_table = pd.DataFrame()
for i in range(4):
print('每個用戶七八月電量%s與三四月電量%s的比值:'%(col[i],col[i]))
ratio_table = date_merge(mon_78[col[i]], mon_34[col[i]], names[i])
summary_table[names[i]] = ratio_table[names[i]]
print(ratio_table,'\n')
print('上述比值整合:\n',summary_table)
實驗結果:
Ps:這里有另外一個注意事項,在jupyter里面看不出來。我再本地又運行了一遍,發現直接按照上述代碼,會報錯。當時就很懵逼,明明jupyter運行很完美,說明程序沒有大問題,但是它在我本地就報錯,我就不開心了。報錯如下:
百度了差不多1個小時,講道理,百度報錯解決辦法是真的難,冷僻的錯誤基本上很少有解決辦法,不過在我堅持不懈下還是找到了。其實它主要問題是出在mean()上,因為他有可能都是0,或者NAN,就會運算不了,jupyter似乎自己解決了?要修正的話,就是numeric_only=False,再取對應列即可,代碼改成如下:
特征合並
將上述統計的所有特征合並在一張表格中顯示出來。
分析過程:
上面寫了好多特征,原始統計量,日差分統計量等等,上面的代碼里面好多個都是直接輸出的,並沒有存儲,而且有些類型是series,不統一。所以第一步就是把他們全部轉化成DataFrame。對於合並,不能列名不能相同,好些個統計量都是重名,當然直接合並也可以,程序會直接區分,給你在后面加個_x,或者_y,但是依然不好區分。所以我寫了個重命名列的函數,在初始列名后面加個標注或者直接重命名了。這里要注意的是min_value和max_value這兩個,這兩個首先要rest_index,不然索引會出問題(又是出錯了n++++++次之后才領悟的)。我采用的是merge函數進行合並,這個類似於數據庫的操作,相比於concat的直接拼接需要用共同的索引連接,但是免去了一個個查看去找要插入的列名。merge()只能完成兩張表的連接,若有三個及以上表,需不斷兩兩合並來實現。
代碼:
# (10)將上述統計的所有特征合並在一張表格中顯示出來
# (concat,merge,join和append)的區別 https://blog.csdn.net/weixin_42782150/article/details/89546357
def rename_columns(df,add):
new_col = list()
for i in df.columns:
if i != 'CONS_NO':
new_col.append(i+add)
else:
new_col.append(i)
df.columns=new_col
return df
df1 ,df2,df3,df4 ,df5= statistics(result.T),statistics(difference.T),statistics(diff_week),min_value.reset_index(),max_value.reset_index()
df6,df7= pd.DataFrame(result.quantile(0.05,axis=1)),pd.DataFrame(max_d)
adds = ['_原始','_日差分','_周差分','_min','_max']
new = ['5%分位數','kwh>0.9max的天數']
dfs=[df1,df2,df3,df4,df5,df6,df7]
all_feature_tables = summary_table
for i in range(len(dfs)):
if i < 5:
a = rename_columns(dfs[i],adds[i])
# print(a.columns)
all_feature_tables = pd.merge(all_feature_tables,a,on='CONS_NO')
elif i < 7:
dfs[i].columns=[new[i-5]]
# print(dfs[i])
all_feature_tables = pd.merge(all_feature_tables,dfs[i],on='CONS_NO')
all_feature_tables.to_csv('all_feature_tables.csv')
實驗結果: