2020中國高校計算機大賽·華爲雲大數據挑戰賽-數據分析(一)

2020中國高校計算機大賽·華爲雲大數據挑戰賽–數據分析(一)

正式賽已經開始幾天了,但這幾天有很多事要忙,所以每什麼時間來做比賽,昨天把數據下下來,結合論壇裏某個小夥伴的baseline簡單分析了下數據。把一些自己的分析記錄在下面,供大家參考,同時能有所啓發得到一些解題的思路。
首先這裏放上baseline的鏈接,感謝姜大德的分享,提供了一份完整的從載入數據,處理數據到訓練模型和提交的完整步驟!
basline鏈接

一、baseline

baseline看過後基本上可以把整個數據處理流程和提交理清楚了,這裏對裏面的一些代碼進行了一些小小的修改,在保證結果一致的情況下,改進了些,加快運算速度。(一般在提取特徵的時候,我們儘量少用apply,因爲它是逐條對數據處理,而應該多用矩陣運算來代替,能少用merge操作儘量少用,因爲它需要匹配標識符,這點我們可以通過sort保持順序,groupby之後的順序是對應的就可以避免merge
這裏舉個小例子,提供參考,這裏是對anchor計算的部分,我改寫成下面這段代碼:

def get_anchor(df):
    tmp=df.groupby('loadingOrder')
    df['lat_diff'] = tmp['latitude'].diff(1)
    df['lon_diff'] = tmp['longitude'].diff(1)
    df['speed_diff'] = tmp['speed'].diff(1)
    df['diff_minutes'] = tmp['timestamp'].diff(1).dt.total_seconds() // 60
    ###直接用numpy的與運算,比apply中if判斷要快的多
    df['anchor'] =((df['lat_diff']<= 0.03)&(df['lon_diff'] <= 0.03)&(df['speed_diff'] <= 0.3)&(df['diff_minutes'] <= 10)).astype('int')
    ###  這裏標記下船幾乎停止的地方
    df['stop']=((df['lat_diff'] <= 0.03)&(df['lon_diff'] <= 0.03)&(df['speed'] <= 1)).astype('int')
#                             
    return df

當然還有很多,比如groupby聚合之後,id的順序和之前的順序是保持一致的,有時候能減少Merge的操作。一些小技巧,在代碼優化方面可以加速特徵運算的效率,畢竟數據量有那麼大,代碼優化方面還是有必要的。

tmp= group['timestamp'].agg({'mmax':'max','mmin':'min'}).reset_index(drop=True)
# 讀取數據的最大值-最小值,即確認時間間隔    
group_df['time_gap'] = (tmp['mmax'] - tmp['mmin']).dt.total_seconds()

二、測試數據分析

trian裏面的數據太大了,先放一邊,我們先看看test裏面有哪些東西。

test_df=pd.read_csv('./A_testData0531.csv')
print('總共有{}艘船'.format(test_df.vesselMMSI.nunique()))
print('{}個快遞運單'.format(test_df.loadingOrder.nunique()))
print('{}個運貨公司'.format(test_df.carrierName.nunique()))
print('{}條運輸路徑'.format(test_df.TRANSPORT_TRACE.nunique()))
print('運輸路段長度:{}'.format(test_df.TRANSPORT_TRACE.apply(lambda x:len(x.split('-'))).unique()))
print('運輸過程中的 spped 情況:{}'.format(test_df.speed.unique()))
print('經度跨越:{}'.format(test_df.longitude.max()-test_df.longitude.min()),'緯度跨越:{}'.format(test_df.latitude.max()-test_df.latitude.min()))
print('測試集中,船隻的運輸的港口:')
print(test_df.TRANSPORT_TRACE.value_counts())
print('測試集時間跨度:')
print('min time:{} max time:{}'.format(test_df.timestamp.min(),test_df.timestamp.max()))

總共有87艘船
222個快遞運單
8個運貨公司
22條運輸路徑
運輸路段長度:[2]
運輸過程中的 spped 情況:[31 30 29 28 32 33 35 34 15 13 11 23 24 26 27 37 36 0 25 14 16 12 8 10
17 18 19 20 9 7 6 5 3 21 22 1 2 40 42 43 41 39 38 4 44 46 47 50
49]
經度跨越:359.046092 緯度跨越:85.676263
測試集中,船隻的運輸的港口:
CNYTN-MXZLO 25685
CNYTN-PAONX 5700
CNSHK-CLVAP 2900
CNYTN-ARENA 2833
CNSHK-MYTPP 1855
CNYTN-MATNG 1694
CNSHK-GRPIR 721
CNYTN-CAVAN 657
CNHKG-MXZLO 613
CNSHK-SGSIN 595
CNYTN-RTM 357
CNSHA-SGSIN 292
CNSHK-SIKOP 266
COBUN-HKHKG 245
HKHKG-FRFOS 223
CNYTN-NZAKL 165
CNSHK-ZADUR 150
CNSHK-ESALG 150
CNSHA-PAMIT 113
CNSHK-PKQCT 104
CNYTN-MTMLA 69
CNSHK-LBBEY 69
Name: TRANSPORT_TRACE, dtype: int64
測試集時間跨度:
min time:2019-01-10T00:27:58.000Z max time:2020-03-27T00:10:08.000Z

上面這段輸出可以基本上了解test中的一些信息了,小夥伴自行read,就不多廢話了。
值得注意的是test裏面的trace基本都是兩段式,但實際可能是不只兩個停靠港口的,中間一般可能會有中轉港
從speed上可以看出,船也不是一直在航線,其中停止的時候也是會有gps記錄的。另外test中的時間跨度還是非常大的,這其中還包括了疫情期間的時間,疫情對運輸應該也有影響,所以時間段也是個很重要的信息。

經緯度座標

這個比賽最重要的信息就是gps經緯度座標,我們先着重分析下。通過查詢test中的經緯度範圍,經度一般在-180~180之間,基本上是橫跨了這個世界地圖(華爲業務遍佈全球 哈哈),緯度範圍是-50到50 ,可以確定是在赤道附近(畢竟海路運輸都是在赤道附近)。這裏對於經緯度不是很熟悉的小夥伴可以百度瞭解下,我也是今天才補的知識 ~ ~。
緯度分北緯南緯,赤道爲分界線。經度分東西,以子午線爲0度,+180至-180.

有了經緯度我們就可以計算距離了,但是計算距離也是個比較麻煩的事情,我們要轉換爲歐式距離纔好有個清晰的距離概念。這裏我給除了計算公式:

C = sin(LatAPi/180)sin(LatBPi/180) + cos(LatAPi/180)cos(LatBPi/180)*cos((MLonA-MLonB)*Pi/180)
Distance = R*Arccos©*Pi/180

哈哈 即使知道公式也還是有點蒙,對吧!
沒關係,這裏我已經寫成了python代碼,只要直接輸入兩點的經緯度就可以計算出來距離,單位是m。
參考的是別人java版本寫的一個python版本的距離計算公式,需要的小夥伴可以拿去:

import numpy as np
def distance(LatA,LatB,LonA,LonB):
    EARTH_RADIUS = 6378.137 # 千米
    def rad(d):
        return d * np.pi/ 180.0
    s=0
    radLatA = rad(LatA)
    radLatB = rad(LatB)
    a = radLatA-radLatB
    b = rad(LonA)-rad(LonB)
    s= 2 * np.arcsin(np.sqrt(np.power(np.sin(a / 2),2)+ np.cos(radLatA) * np.cos(radLatB)*np.power(np.sin(b / 2),2)))
    s=s* EARTH_RADIUS
    #  保留兩位小數
    s = np.round(s * 100)/100
    s = s * 1000 # 轉換成m
    return s

好的!! 接下來我們具體吧一個運單根據經緯座標畫出來看看是怎樣的:
在這裏插入圖片描述
紅色點表示船幾乎停止,綠色是起始點,藍色是重點。
gps數據來看,還是比較規範的,沒有特別多的離羣點。
但是這樣總歸不好看,不知道該軌跡的具體位置,下面我們放到世界地圖上看看到底是怎樣的一條軌跡。
這裏選取了一種trace的多個運單,爲了對比他們的起始和終止點是否一致!
在這裏插入圖片描述
這張圖就可以很明顯看到,船從深圳出發,跨國了大洋彼岸,來到了大概像是阿根廷的地方!
我們可以看到,起始點的座標基本上是一致的,但是終點卻不是那麼一致,有的在遠處就沒有了。(注意這裏我取的是trian裏面的數據,因爲train裏面纔是完整的Gps路由信息,test是截斷了的(因爲要預測到港時間!不截斷還預測什麼哈哈!))
在這裏插入圖片描述
所以在構造訓練集的時候,我們要充分考慮清楚一下幾個問題:
1、船到港的確切時間該如何計算。(肯定會有誤差)
2、訓練集的構建應該和test一致,test中是截斷了的,所以trian中的數據也要人爲截斷一個完整路由來作爲一個訓練樣本。
3、確定目的港口的gps座標,或者說是目的停靠點位置(這樣我們才能計算出當前段落距離目的地的距離)

要考慮的問題還挺多的,從題目來看,做數據清洗,和構造是主要工作,我們應該仔細讀題,從賽方給我我們的海量數據中儘可能多的提取有用信息。

這裏在補幾張圖,我在trian數據裏面畫了幾張相同trace路線的loadingOder的終點,可以看出,雖然路線是一樣的,但是不同的運輸單終點還是有一定差距的(終點並不是很聚合)
在這裏插入圖片描述
下面是相同trace,不同loadingOrder的路勁,也可以看出 終點位置是有差距的,同時每條數據的timestamp並不是等間距的,就也就是位置數據獲取的頻率是變化的。
在這裏插入圖片描述

總結

從上面的分析我們對數據有了一些瞭解,對於訓練數據需要考慮的問題也在上面說了。由於train數據太大,幾乎不可能一次性讀取,所以只能選擇批量讀取,同時從裏面抽取我們需要的數據保存下來。pandas批量讀取只要在read_csv中設置chunksize的大小就可以了,這裏放上示例,小夥伴們可以自由發揮:

import pandas as pd
from stqdm import tadm
train_flux=pd.read_csv('./train0523.csv',chunksize=10000,names=names)
## 注意 train裏面的yundan不是按順序放好的,所以要讀取完整的loadingOrder數據最好從頭到尾掃描一遍
count=0
loadingOrders=[] # 可以用來記錄讀取了多少個loadingOrders
train_df=pd.DataFrame(columns=names)
for data in tqdm(train_flux):
	#這部分可以是你對data的處理
	train_df.append(data)
train_df.head()
這裏在放上一個減少內存的代碼,輸入DataFarme即可,裏面小夥伴也可以根據需要自行修改
def reduce_mem_usage(props):
    # 計算當前內存
    start_mem_usg = props.memory_usage().sum() / 1024 ** 2
    print("Memory usage of the dataframe is :", start_mem_usg, "MB")
    
    # 哪些列包含空值,空值用-999填充。why:因爲np.nan當做float處理
    NAlist = []
    for col in props.columns:
        # 這裏只過濾了objectd格式,如果你的代碼中還包含其他類型,請一併過濾
        if (props[col].dtypes != object):
            
            print("**************************")
            print("columns: ", col)
            print("dtype before", props[col].dtype)
            
            # 判斷是否是int類型
            isInt = False
            mmax = props[col].max()
            mmin = props[col].min()
            
            # # Integer does not support NA, therefore Na needs to be filled
            # if not np.isfinite(props[col]).all():
            #     NAlist.append(col)
            #     props[col].fillna(-999, inplace=True) # 用-999填充
                
            # test if column can be converted to an integer
            asint = props[col].fillna(0).astype(np.int64)
            result = np.fabs(props[col] - asint)
            result = result.sum()
            if result < 0.01: # 絕對誤差和小於0.01認爲可以轉換的,要根據task修改
                isInt = True
            
            # make interger / unsigned Integer datatypes
            if isInt:
                if mmin >= 0: # 最小值大於0,轉換成無符號整型
                    if mmax <= 255:
                        props[col] = props[col].astype(np.uint8)
                    elif mmax <= 65535:
                        props[col] = props[col].astype(np.uint16)
                    elif mmax <= 4294967295:
                        props[col] = props[col].astype(np.uint32)
                    else:
                        props[col] = props[col].astype(np.uint64)
                else: # 轉換成有符號整型
                    if mmin > np.iinfo(np.int8).min and mmax < np.iinfo(np.int8).max:
                        props[col] = props[col].astype(np.int8)
                    elif mmin > np.iinfo(np.int16).min and mmax < np.iinfo(np.int16).max:
                        props[col] = props[col].astype(np.int16)
                    elif mmin > np.iinfo(np.int32).min and mmax < np.iinfo(np.int32).max:
                        props[col] = props[col].astype(np.int32)
                    elif mmin > np.iinfo(np.int64).min and mmax < np.iinfo(np.int64).max:
                        props[col] = props[col].astype(np.int64)  
            else: # 注意:這裏對於float都轉換成float16,需要根據你的情況自己更改
                props[col] = props[col].astype(np.float16)
            
            print("dtype after", props[col].dtype)
            print("********************************")
    print("___MEMORY USAGE AFTER COMPLETION:___")
    mem_usg = props.memory_usage().sum() / 1024**2 
    print("Memory usage is: ",mem_usg," MB")
    print("This is ",100*mem_usg/start_mem_usg,"% of the initial size")
    return props#, NAlist
##示例
train_df=reduce_mem_usage(train_df)
由於時間比較有限,我也只是簡單的分析測試了下數據,並沒有詳細分析,後續如果有好的idel以及建模思路,會繼續在Blog上與大家分享,如果有不對的地方,歡迎大家批評指正。

#----------------------------------------------我是分割線!!!-----------------------------------------------------
對了 關於提交文件的格式,賽方說linux下保存會出問題,所以我的解決辦法是down到windos本地下,在用pands打開保存下就可以了。然後需要注意的就是timestamp的格式需要和官方的一致纔行,這裏是baseline裏面,我對裏面進行了改進,親測提交有效哦:

test_df = test_df.merge(result, on='loadingOrder', how='left')
# 這裏是放入ETA數據,根據需要可自行改寫
test_df['ETA']=(test_df['onboardDate'] + test_df['label'].apply(lambda x:pd.Timedelta(seconds=x))).apply(lambda x:x.strftime('%Y/%m/%d  %H:%M:%S'))

test_df.drop(['direction','TRANSPORT_TRACE'],axis=1,inplace=True)
test_df['onboardDate'] = test_df['onboardDate'].apply(lambda x:x.strftime('%Y/%m/%d  %H:%M:%S'))
test_df['creatDate'] = pd.datetime.now().strftime('%Y/%m/%d  %H:%M:%S')
test_df['timestamp'] =test_df['timestamp'].apply(lambda x:x.strftime('%Y-%m-%dT%H:%M:%S.000Z'))# 注意這裏的時間格式哦
# 整理columns順序
result = test_df[['loadingOrder', 'timestamp', 'longitude', 'latitude', 'carrierName', 'vesselMMSI', 'onboardDate', 'ETA', 'creatDate']]
# result.to_csv('result.csv',index=False,)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章