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,)

2020中国高校计算机大赛·华为云大数据挑战赛-数据分析(二)

猜你喜欢

转载自blog.csdn.net/qq_26593695/article/details/106557941
今日推荐