本次任务解决两个问题:
1)h3编码转换,获得站点的经纬度以及其他地理数据
2)处理训练集中的站点发电量的异常值
1、h3+逆编码转化
首先安装h3(pip install h3),需要看地图再安装一个folium(pip install folium),然后开始写代码转换。
from h3 import h3
from IPython.display import display
df_stub_train = pd.read_csv('./data1/train/stub_info.csv')
df_stub_test = pd.read_csv('./data1/test/stub_info.csv')
df_stub_train['center'] = df_stub_train['h3'].apply(lambda x: h3.h3_to_geo(x))
df_stub_test['center'] = df_stub_test['h3'].apply(lambda x: h3.h3_to_geo(x))
#print(df_stub.head())
# 绘制地图
import folium
def geo_map(df):
# 拆分经纬度坐标
df[['latitude', 'longitude']] = pd.DataFrame(df['center'].tolist(), columns=['latitude', 'longitude'])
# 创建地图
m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=10)
# 添加标记点
for index, row in df.iterrows():
folium.Marker(
location=[row['latitude'], row['longitude']],
popup=row['id_encode']
).add_to(m)
# 显示地图
return m
stub_m_train = geo_map(df_stub_train)
stub_m_test = geo_map(df_stub_test)
#display(stub_m)
stub_m_train.save('map.html')
用到了h3_to_geo(),它是一个将 H3 地理编码转换为经纬度坐标的函数。将训练集和测试集的站点进行h3解码,再将经度、纬度单独划分成列作为特征。
为了获取更多地理信息,去百度地图申请一个全球逆编码转换api接口。
####################################### 调取百度api获取经纬度对应地址信息
# 需要先进入百度web服务api网站注册账号, 获取对应的ak 跟sk, 需阅读网站指引
# https://lbsyun.baidu.com/faq/api?title=webapi
# 里面有不少案例, 以下代码也是直接引用上面提供的案例代码
# 直接用的话需要提供代码中的 ak 跟 sk 两个key
# encoding:utf-8
# 根据您选择的AK已为您生成调用代码
# 检测您当前的AK设置了sn检验,本示例中已为您生成sn计算代码
import requests
import urllib
import hashlib
# 建立访问函数
def get_city_frombd(row):
# 服务地址
host = "https://api.map.baidu.com"
# 接口地址
uri = "/reverse_geocoding/v3"
# 此处填写你在控制台-应用管理-创建应用后获取的AK
ak = "OkLKwnGpGvG7QnzA1tHQPi1KLGHS37Og"
# 此处填写你在控制台-应用管理-创建应用时,校验方式选择sn校验后生成的SK
sk = "KGczW3bY6CxqAaRZm7C8R7oTXMXqCgXD"
# 设置您的请求参数
coordinate = str(row['latitude']) + ',' + str(row['longitude'])
params = {
"ak": ak,
"output": "json",
"coordtype": "wgs84ll",
"extensions_poi": "0",
"location": coordinate,
}
# 拼接请求字符串
paramsArr = []
for key in params:
paramsArr.append(key + "=" + params[key])
queryStr = uri + "?" + "&".join(paramsArr)
# 对queryStr进行转码,safe内的保留字符不转换
encodedStr = urllib.request.quote(queryStr, safe="/:=&?#+!$,;'@()*[]")
# 在最后直接追加上您的SK
rawStr = encodedStr + sk
# 计算sn
sn = hashlib.md5(urllib.parse.quote_plus(rawStr).encode("utf8")).hexdigest()
# 将sn参数添加到请求中
queryStr = queryStr + "&sn=" + sn
# 请注意,此处打印的url为非urlencode后的请求串
# 如果将该请求串直接粘贴到浏览器中发起请求,由于浏览器会自动进行urlencode,会导致返回sn校验失败
url = host + queryStr
response = requests.get(url)
return response.json().get('result')#.get('addressComponent')
def df_stub_process(df):
df['center'] = df['h3'].apply(lambda x: h3.h3_to_geo(x))
df[['latitude', 'longitude']] = pd.DataFrame(df['center'].tolist(), columns=['latitude', 'longitude'])
df['latitude'] = df['latitude'].apply(lambda x: round(x,6))
df['longitude'] = df['longitude'].apply(lambda x: round(x,6))
# 通过百度api返回城市信息
df['address'] = df[['latitude','longitude']].apply(get_city_frombd, axis = 1)
return df
df_stub_train = df_stub_process(df_stub_train)
df_stub_test = df_stub_process(df_stub_test)
以上代码为网站提供,只需定义一个函数调用即可,运行后就可以得到站点的地理信息,例如站点0的地理信息如下:
'location': {'lng': 120.10799201951572, 'lat': 31.52773196756505}, 'formatted_address': '江苏省常州市武进区陆马公路',
'edz': {'name': ''},
'business': '',
'addressComponent': {'country': '中国', 'country_code': 0, 'country_code_iso': 'CHN', 'country_code_iso2': 'CN', 'province': '江苏省', 'city': '常州市', 'city_level': 2, 'district': '武进区', 'town': '雪堰镇', 'town_code': '320412110', 'distance': '', 'direction': '', 'adcode': '320412', 'street': '陆马公路', 'street_number': ''},
'pois': [],
'roads': [],
'poiRegions': [],
'sematic_description': '',
'formatted_address_poi': '',
'cityCode': 348
2、处理发电量异常值
首先筛选一些站点的发电量值绘制曲线,观察大致趋势。这里选择0,1,2,3,4五个站点,代码同任务一,一年中每天的充电量曲线如下:
由上图可以发现,站点0和站点2都出现了异常低的充电量数值,站点4出现异常高的充电量数值。所以决定先确定异常值的具体位置,再用各自对应站点一年发电量的平均值来替代。
#寻找异常值,先挑选一个大致范围
tmp_df1=train_df[train_df['id_encode']==0]['power']
print(tmp_df1[260:270])
tmp_df2=train_df[train_df['id_encode']==2]['power'].reset_index(drop=True)
tmp_df2=tmp_df2.reset_index(drop=True).reset_index()
print(tmp_df2[220:230])
tmp_df3=train_df[train_df['id_encode']==4]['power'].reset_index(drop=True)
tmp_df3=tmp_df3.reset_index(drop=True).reset_index()
print(tmp_df3[75:90])
通过观察输出的结果列表,确定异常数据位置:
1)站点0第261行(即第262天)充电量为0,异常低,填充站点0均值。
2)站点2在第225-第237行充电量为0,异常低,填充站点2均值 (不重新索引即为955-967行)
3)站点4在83行充电量为1090.4430,第84行充电量为534.8400,第85行充电量为2126.4200,异常高,填充站点4均值 (不重新索引即为1543-1545行)
#更改异常值
train_df['power'].iat[261]=train_df.loc[train_df['id_encode'] == 0, 'power'].mean()
print(train_df['power'].iloc[261])
train_df.loc[955:967,'power']=train_df.loc[train_df['id_encode'] == 2, 'power'].mean()
print(train_df.loc[955:967,'power'])
train_df.loc[1543:1545,'power']=train_df.loc[train_df['id_encode'] == 4, 'power'].mean()
print(train_df.loc[1543:1545,'power'])
接着对应更改异常值,再重新观察更改过后的五个站点一年内的充电量曲线,可以看到已取去除尖刺,大致在一个范围内波动。
最后将这些处理过的特征重新聚合,再次训练即可。
本次学习任务不足即待改进之处:
1)写完打卡文章才发现在处理异常值时只考虑到了前5个站点,得改进。
2)只在任务一的基础上加了经纬度特征,得把逆编码解码后的更多地理信息也划分成数据集,不然无引入逆编码的意义。
3)提交后发现分数并未大幅提高,甚至在做完异常值平均处理后分数下降了不少,感觉是异常值的处理出了问题,这一点也需要继续思考。