プロジェクトの背景
スマートフォンの人気と携帯電話ユーザーの急増に伴い、グリーン環境保護、利便性と効率性、経済性と環境保護という特徴を備えたシェア自転車は、都市交通システムの重要な部分として繁栄しています。シェア自転車会社は、キャンパス、バス停、住宅街、公共サービスエリアなどでサービスを提供することで、交通業界の「パズル」の最後のピースを完成させ、他の公共交通手段との相乗効果を生み出します。シェア自転車は、都市部の短距離交通移動や「ラストワンマイル」問題の軽減に役立ちますが、その運用上の特徴により、都市部での自転車の配備と配車の計画と管理において、企業にとって大きな課題となります。
本稿では、上記の背景を踏まえ、2016年8月に上海Mobikeから無作為抽出した約10万台のオープンオーダーデータを分析し、その裏にある法則を掘り起こし、Mobikeシェア自転車の利用状況と現状を概説する。これにより、Mobike シェア自転車会社は、マーケティング戦略をより適切に開始し、新しい自転車乗り出しエリアを見つけ、車両レイアウトを規制し、ユーザーへのサービスを向上させることができます。
注: このプロジェクトのデータ、コード、チャートにはスタンプを押すことができます: Mobike シェア自転車データ分析プロジェクト データ、コード、チャートのダウンロード
データ探索
このステップでは、データの全体像を見て、データを一般的に理解し、データの質の高い探索と特徴分析を実行します。
データの読み取りとデータセット データの表示
import pandas as pd
from math import radians, cos, sin, asin, sqrt,ceil
import numpy as np
import geohash
#数据读取
data = pd.read_csv("./mobike_shanghai_sample_updated.csv")
print(data.head(10))
このデータセットは、自転車シェアリング会社 Mobike によって提供されており、同社は 2016 年 8 月に上海で 10 万件を超えるユーザーのサイクリング データ (注文データ) をランダムにサンプリングしました。これには、取引番号、ユーザー ID、車両 ID、出発地の経度および緯度が含まれます。乗車、乗車目的地の経度・緯度、貸出時間、返却時間、馬場の経度・緯度等のデータ。
データセットの属性タイプを確認すると、レンタル時間と返却時間のデータタイプがオブジェクトタイプであることがわかり、日時タイプに変換できます。
print(data.info())
data['start_time'] = pd.to_datetime(data['start_time'])
data['end_time'] = pd.to_datetime(data['end_time'])
print(data.info())
データセット内の null 値の分布を確認し、データ内に null 値がないことを確認します。
print(data.isnull().sum())
データマイニング
このデータセットには 100,000 個以上の自動車トランザクション データが含まれていますが、各自動車トランザクション データには 10 個の特徴しかなく、このデータ セットは以前のムービー データ セットとは異なります。は他の特徴と明らかな相関関係を持たない独立した情報フィールドですが、データセットの特徴には明らかな相関関係があります特徴間の関係、または 1 つの特徴から反映された複数の特徴を関連付けて組み合わせることで、新しい特徴を取得できます。データセットを拡張し、物事の複数の側面を習得し、データのより潜在的な法則を掘り出すための新機能により、その後のデータ分析でより次元の高い分析を実行し、データをより深く理解できるようになります。
新しい機能を見つける方法:
- レンタル時間+返却時間 => 乗車時間
- ライドの開始点の経度および緯度 + ライドの終了点の経度および緯度 => ライドの移動距離
- Mobike ライディング トラックの経度と緯度 => ライディング パス
- レンタル時間 => 曜日 (つまり、各配車注文が発生する曜日) + 期間 (つまり、各注文が 1 日 24 時間のうちどの時間帯に発生するか)
- 乗車時間 ⇒ 注文金額(概算)
- 各注文金額 + 各注文のレンタル時間 + 各注文のユーザー ID => ユーザー分類 (RFM モデル)
- ライドの開始点と終了点の経度と緯度 => ライドの開始点と終了点が位置する地域
レンタル時間+返却時間 => 乗車時間
開始時刻から終了時刻までの乗車時間を計算するための「ラグ」列が新たに追加され、時間の単位が分に統一されました。
data["lag"] = (data.end_time - data.start_time).dt.seconds/60
ライドの開始点の経度および緯度 + ライドの終了点の経度および緯度 => ライドの移動距離
「距離」列を追加して、乗車の開始点と終了点の緯度と経度をキロメートル単位で計算して、乗車の変位を取得します。
geo distance() は、2 点の緯度と経度から 2 点間の直線距離を計算する数式をカプセル化します。導出プロセスとその背後にある式の数学的原理に興味がある場合は、https://blog.csdn.net/sunjianqiang12345/article/details/60393437をクリックして詳細をご覧ください。
def geodistance(item):
lng1_r, lat1_r, lng2_r, lat2_r = map(radians, [item["start_location_x"], item["start_location_y"], item["end_location_x"], item["end_location_y,"]]) # 经纬度转换成弧度
dlon = lng1_r - lng2_r
dlat = lat1_r - lat2_r
dis = sin(dlat/2)**2 + cos(lat1_r) * cos(lat2_r) * sin(dlon/2)**2
distance = 2 * asin(sqrt(dis)) * 6371 * 1000 # 地球平均半径为6371km
distance = round(distance/1000,3)
return distance
#data按行应用geodistance()得到distance列的数值
data["distance"] = data.apply(geodistance,axis=1)
Mobike ライディング トラックの経度と緯度 => ライディング パス
「adderLength」列が追加され、「track」列のデータを計算することでサイクリング パスが取得されます。
馬場の文字列を「#」区切りでリスト化し、
馬場の経度・緯度情報リストを取得します。馬場のリストをペアでポーリングし続けることで、毎回2つのリスト要素が順番に取り出されます(前トラックのサンプリングポイント(前トラックのサンプリングポイントの経度・緯度、後トラックのサンプリングポイントの緯度・経度)を「,」で区切って4つの値(前トラックのサンプリングポイントの経度)を取得します。前方軌道、前方軌道のサンプリング ポイントの緯度、後方軌道のサンプリング ポイントの経度、後方軌道のサンプリング ポイントの緯度) が項目辞書に結合され、 geo distance() に渡されます。 2 点の変位を計算し、各小さなセグメントの変位を合計してライディング パスを取得します。
#通过摩拜单车的踪迹获取每次交易骑行的路径
def geoaadderLength(item):
track_list = item["track"].split("#")
adderLength_item = {
}
adderLength = 0
for i in range(len(track_list)-1):
start_loc = track_list[i].split(",")
end_loc = track_list[i+1].split(",")
adderLength_item["start_location_x"],adderLength_item["start_location_y"] = float(start_loc[0]),float(start_loc[1])
adderLength_item["end_location_x"],adderLength_item["end_location_y"] = float(end_loc[0]),float(end_loc[1])
adderLength_each = geodistance(adderLength_item)
adderLength = adderLength_each + adderLength
return adderLength
data["adderLength"] = data.apply(geoaadderLength,axis=1)
レンタル時間 ⇒ 曜日+時間帯(24時間制)
「平日」(つまり、各乗車注文が発生する曜日)と「時間」(つまり、各注文が 1 日 24 時間のうちのどの時間帯に発生するか)を追加しました。
data['weekday'] = data.start_time.apply(lambda x: x.isoweekday())
data['hour'] = data.start_time.apply(lambda x: x.utctimetuple().tm_hour)
乗車時間 => 注文金額
「料金」欄を追加 各注文の乗車時間に応じて概算の注文金額を表示 2016年Mobikeの料金基準を参考に、30分ごとに1元が課金されます。
data['cost'] = data.lag.apply(lambda x: ceil(x/30))
注文金額 => ユーザー分類 (RFM モデル)
各トランザクションのユーザー ID、消費量、消費時間がわかっているので、RFM モデルを使用してユーザーを分類することを検討できます。ここで採用するモデルは RFM モデルです。
RFMモデルとは、ユーザーの価値を細分化する手法であり、ユーザーを研究するために使用される数理モデルです。
- R (Recency) 最終消費時間: ユーザーが最後に消費してから現在までの時間を示します。
- F (Frequency) 消費頻度: 消費頻度とは、統計期間内にユーザーが商品を購入する回数を指します。
- M (Monetary) 消費額: 消費額とは、統計期間内にユーザーが消費した合計金額を指し、消費者が企業にどれだけ稼いだかを反映します。
これらの 3 つの側面は、ユーザーを 8 つの標準カテゴリに分類するのに役立ちます。
モデル構築プロセスは次のステップに分割できます。
- RFM値を計算する
R 値: 各ユーザーが最後にシェア自転車をレンタルしてからの 9 月 1 日以降の日数 (データ セットには 2016 年 8 月のデータのみが含まれているため、最新の統計をカウントするために 9 月 1 日の時点ノードに立っています)各ユーザーの一回の消費から現在までの時間)
F値:各ユーザーの累積レンタル頻度。
M値:つまり、各ユーザーの累積消費量。
#因数据集仅包含八月份发起的订单数据,故以9月1日为R值计算基准
data['r_value_single'] = data.start_time.apply(lambda x: 32 - x.timetuple().tm_mday)
# 按每个用户id所有订单日期距9/1相差天数的最小值作为r值
r_value = data.groupby(['userid']).r_value_single.min()
f_value = data.groupby(['userid']).size() # 按每个用户id八月累积订单数量作为f值
m_value = data.groupby(['userid']).cost.sum() # 按每个用户id八月累积消费金额作为m值
#把r值、f值、m值组合成DataFrame
rfm_df = pd.DataFrame({
'r_value':r_value,'f_value':f_value,"m_value":m_value})
- RFM スコアの計算
このステップでは、各ユーザーの RFM 値をスコアリングします。スコアのサイズは好みによって異なります。RFM 値が高いほど、RFM スコアも高くなります。RFM モデルにおける採点は一般的に 5 点法が採用されており、RFM 値の区間区分に応じて RFM スコアが設定されます。
rfm_df["r_score"] = pd.cut(rfm_df["r_value"],5,labels=[5,4,3,2,1]).astype(float)
rfm_df["f_score"] = pd.cut(rfm_df["f_value"],5,labels=[1,2,3,4,5]).astype(float)
rfm_df["m_score"] = pd.cut(rfm_df["m_value"],5,labels=[1,2,3,4,5]).astype(float)
- ユーザーの次元を分割する
r_score、f_score、m_score は 1 ~ 5 になります。これら 3 つの値を 111、112、113... のように組み合わせると、125 (5 * 5 * 5) 個の結果を組み合わせることができますが、分類と非分類が多すぎます。分類は本質的に同じであるため、ユーザー ディメンションの分割では、グループ化の結果を単純化します。各顧客の R、F、M スコアが平均より大きいかどうかを判断することで、グループ化の結果を簡素化します。各顧客の RFM 値と RFM 平均値を比較すると、結果は 0 と 1 の 2 つだけです (0 は平均値より小さいことを意味し、1 は平均値より大きいことを意味します)。グループは 8 つあります (2 * 2)。 ※2)全体の組み合わせにおいて。
#后面*1是为了把布尔值false和true转成0和1
rfm_df["r是否大于均值"] = (rfm_df["r_score"] > rfm_df["r_score"].mean())*1
rfm_df["f是否大于均值"] = (rfm_df["f_score"] > rfm_df["f_score"].mean())*1
rfm_df["m是否大于均值"] = (rfm_df["m_score"] > rfm_df["m_score"].mean())*1
#把每个用户的rfm三个指标统合起来
rfm_df["class_index"] = (rfm_df["R是否大于均值"]*100) + (rfm_df["f是否大于均值"]*10) + (rfm_df["m是否大于均值"]*1)
def transform_user_class(x):
if x == 111:
label = "重要价值用户"
elif x == 110:
label = "消费潜力用户"
elif x == 101:
label = "频次深耕用户"
elif x == 100:
label = "新用户"
elif x == 11:
label = "重要价值流失预警用户"
elif x == 10:
label = "一般用户"
elif x == 1:
label = "高消费唤回用户"
elif x == 0:
label = "流失用户"
return label
rfm_df["user_class"] = rfm_df["class_index"].apply(transform_user_class)
これまでのところ、RFM モデルはユーザーを 8 つの標準カテゴリに分類します。8 つのカテゴリの対応する意味は次のとおりです。
取得したユーザー分類結果をユーザー ID に従って元のデータにつなぎ合わせ、「user_class」という名前を付けます。桁
data = data.merge(rfm_df["user_class"], on = 'userid', how = 'inner')
ライドの開始点と終了点の経度と緯度 => ライドの開始点と終了点が位置するブロック
このステップでは、乗車の開始点と終了点の緯度と経度を GeoHash でエンコードされた文字列に変換します。
GeoHash は、2 次元の緯度と経度を GeoHash でエンコードされた文字列に変換し、各文字列は特定の長方形の領域を表します。この長方形領域内のすべての点 (緯度と経度の座標) は、同じ GeoHash 文字列を共有します。GeoHash は本質的にハッシュ関数ですが、ハッシュの衝突を回避しようとする他のハッシュ関数とは異なり、GeoHash は、緯度と経度の範囲に従って、異なる経度および緯度のハッシュを同じ文字列に衝突させます。
Geohash は、任意の緯度と経度のセグメンテーション レベルを提供でき、通常は 1 ~ 12 レベルに分割されます。Geohash エンコードされた文字列が長いほど、表現される領域の範囲と、Geohash エンコードされた文字列の長さに対応するブロック範囲がより正確になります。
Mobike シェア自転車の短距離走行特性を考慮して、変換には GeoHash 6 ビット エンコードを選択します。
Geohash は変換前にインストールする必要があります
pip install geohash
そして、python/Lib/site-packages/ ディレクトリに移動し、Geohash フォルダーの名前を geohash に変更してから、このディレクトリ内の init.py ファイルを変更し、geohash から .geohash に変更して、通常どおりインポートします (これはバグです) )。
import geohash
#经纬度转geohash
def transform_start_geohash(item):
return geohash.encode(item["start_location_x"], item["start_location_y"],6)
def transform_end_geohash(item):
return geohash.encode(item["end_location_x"], item["end_location_y"],6)
data["geohash_start_block"] = data.apply(transform_start_geohash,axis = 1)
data["geohash_end_block"] = data.apply(transform_end_geohash,axis = 1)
変換された GeoHash エンコード文字列を見てください。
print(data.loc[:,["geohash_start_block","geohash_end_block"]])
拡張されたデータセットを表示する
拡張されたデータセットを表示して保存します
print(data.head(10))
data.to_csv("./data_dig.csv",index = None)
拡張されたデータセットには、次の情報フィールドが追加されます。
lag(乗車時間)、Distance(乗車排気量)、adderLength(乗車経路)、weekday(乗車曜日)、hour(乗車時間帯)、cost(今回の乗車の消費量)、r_value_single(9月1日からの日数)注文開始時)、user_class(ユーザー分類)、geohash_start_block(ライドの開始点が位置するブロックのGeoHashコード文字列)、geohash_end_block(ライドの終了点が位置するブロックのGeoHashコード文字列)。
データ分析
データマイニングによって拡張されたデータセットについて、次の次元から分析します。
時間次元
- 乗車時間の分布分析
from pyecharts import options as opts
from pyecharts.charts import Bar
import pandas as pd
#读取数据
data = pd.read_csv("./data_dig.csv")
#骑行时长分布
lag_data = data["lag"].value_counts()
lag_c = (
Bar()
.add_xaxis(lag_data.index.tolist())
.add_yaxis("骑行时长",lag_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="骑行时长分布图"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
lag_c.render_notebook()
このグラフから、データには明らかな長い裾があり、右に歪んだ分布を示していることがわかります (正規分布と歪んだ分布はどちらも正常な現象ですが、なぜデータが歪んだ分布を示すのでしょうか? シェア自転車を利用する人の行動が影響しているためです)旅行自体に「偏心」が含まれており、短期間の旅行になる傾向があります); データの異常な記録のせいで、X 軸のデータ範囲は、X 軸のデータ範囲スパンよりも非常に大きくなります。このようなデータ記録は、ユーザーが走行中にロックを切り忘れたことが原因である可能性があります。
データ内の走行時間データの異常処理については、新たに走行速度「速度」(走行経路・走行時間)の列を追加し、明らかな走行速度の異常を大まかに排除することを検討します(走行速度は一般的には12~20km/h)走行後のロックの閉め忘れによる異常記録を解消します。次に、ヒストグラムを再描画して、乗車時間のデータ分布を観察します。
#新增骑行速度,通过剔除骑行速度存在异常的记录处理异常数据,得到新的骑行时长
data['speed'] = data['adderLength'] / (data['lag'] / 60)
data = data[-(((data['speed'] < 12) | (data['speed'] > 20)) )]
lag_data = data["lag"].value_counts().sort_index()
len(lag_data)
lag_c = (
Bar()
.add_xaxis(lag_data.index.tolist())
.add_yaxis("骑行时长",lag_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="骑行时长分布图"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
lag_c.render_notebook()
図のようにロングテールが明らかに弱くなっており、
ユーザーの乗車時間は毎回30分以内に集中しているため、この分布をターゲットにして30分程度の割引クーポンを発行することができます。
- 24時間循環次数分布分析
hour_data = data["hour"].value_counts().sort_index()
hour_c = (
Bar()
.add_xaxis(hour_data.index.tolist())
.add_yaxis("骑行时长",hour_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="骑行时长分布图"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
hour_c.render_notebook()
次の図からわかります。
23時から5時までは休憩する人が多く、シェア自転車の利用注文は非常に少ないですが、6時台から徐々に注文が増え始め、18時からは通勤時間帯となります。 7時から9時と17時から20時の間に注文数が現れます小さなピークはそれぞれ朝のピークと夕方のピークであり、11時から14時の間は地元の昼のピークがあり、それは正午の外食や休憩中の活動に関係しています。
全体的な傾向としては、シェア自転車の自転車交通が人々の通勤に大きく貢献していることがわかります。
- 営業日・休日の24時間乗車注文分布分析
上記で計算した24時間乗車オーダーの分布は、就業日と休日の差を無視しており、人々の通勤状況は就業日と休日で異なるため、就業日と休日では、配車注文は 24 時間配信されますか?
#分别提取非工作日和工作日的订单数据
isweekday_data = data[(data["weekday"]==6) | (data["weekday"]==7)]
noweekday_data = data[~((data["weekday"]==6) | (data["weekday"]==7))]
#找出数据集中包含多少天非工作日的数据和多少天工作日的数据
isweekday_num = isweekday_data["weekday"].value_counts().shape[0]
noweekday_num = noweekday_data["weekday"].value_counts().shape[0]
print("数据集中工作日的天数:" + isweekday_num)
print("数据集中非工作日的天数:" + noweekday_num)
#工作日5天,非工作日2天
#分别计算出工作日、非工作日每个小时的平均订单量
isweekday_hour_data = isweekday_data.groupby("hour").count()["orderid"]/isweekday_num
noweekday_hour_data = noweekday_data.groupby("hour").count()["orderid"]/noweekday_num
isAndNoweekday_hour_c = (
Bar()
.add_xaxis(hour_data.index.tolist())
.add_yaxis("工作日24h订单数分布",noweekday_hour_data.values.tolist())
.add_yaxis("非工作日24h订单数分布",isweekday_hour_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="工作日和非工作日的24h订单数分布"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
isAndNoweekday_hour_c.render_notebook()
緑色のバーは、営業日中の 1 時間あたりの平均注文量の分布を表します。時間当たりの総注文数の分布も上記と同様で、23時から5時まではシェア自転車の注文数が非常に少なく、6時以降から徐々に注文数が増加し始めます。 ; 7:00 ~ 17:00 の間 非番時間には、注文数には朝のピークと夕方のピークという小さなピークがあり、11 ~ 14 時の時間帯には、地元の正午のピークがあります。
紫色のバーは、非営業日の 1 時間あたりの平均注文量の分布です。休日の自転車交通量は非通勤交通量が大半を占めており、平日の分布と比較すると比較的平坦な分布となっており、朝夕のピーク現象は見られない。
空間次元
- 自転車走行距離(経路)分布解析
#骑行距离单位为千米
adderLength_data = data["adderLength"].value_counts().sort_index()
adderLength_c = (
Bar({
"theme": ThemeType.MACARONS})
.add_xaxis([round(i,2) for i in adderLength_data.index.tolist()])
.add_yaxis("骑行距离",adderLength_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="骑行距离分布图"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
adderLength_c.render_notebook()
サイクリング距離分布図を見ると、主なサイクリング距離はおそらく 3 キロメートル程度の範囲に集中していることがわかります。ただし、長距離または 40 キロメートルを超える走行もあり、これらは異常データと見なすことができ、または他の特殊な走行現象として理解できます。データから外れ値を削除するには、まず外れ値を特定する必要があります。統計では、箱ひげ図は外れ値を判断するためによく使用されます。箱ひげ図はデータの正規分布を必要とせず、幅広い用途に使用できます。箱ひげ図を使用して外れ値を特定する前に、まず箱ひげ図について理解する必要があります。
箱ひげ図は、その形状が箱に似ていることから名前が付けられ、主に元のデータの分布の特徴を反映するために使用され、複数のデータ グループの分布の特徴を比較することもできます。箱ひげ図を通じて、最大値(上限)、最小値(下限)、中央値Q2、上位四分位Q3、下位四分位Q1、および異常値を直観的に読み取ることができます。値(範囲外の値)上限内限および下限内限外)。なお、図中の上限、下限は内上限、内下限を指しており、ボックス図を描く場合には外上限(最大値)、外下下限(最小値)のみを表示する場合が多いですが、式 Q3 + 1.5*IQR は上限と内側の制限を計算し、式 Q1-1.5 * IQR は下限と内側の制限を計算します。
外れ値を箱ひげ図の上限と下限で割る数学的原理は次のとおりです。 正規分布を例にとると、データのセットが正規分布に従う場合、X が (μ-3σ, μ) の外に入る確率は次のようになります。第三に、実際の問題では、対応する事象は起こらない(外れ値)と考えられる場合が多く、基本的には区間(μ-3σ、μ+3σ)が実際に起こり得る値とみなすことができます。確率変数 X の値の範囲。これは、正規分布の「3σ」(3 シグマ) 原則と呼ばれます。ただし、箱ひげ図の上限と下限の範囲はちょうど (μ-3σ、μ+3σ) の間にあるため、箱ひげ図の上限と下限を超えるデータは外れ値であると判断できます。
シェア自転車の走行距離データの外れ値を迅速に判断し、外れ値を迅速に除去するための箱ひげ図を作成します。
図から、描かれた箱ひげ図の箱が非常に平坦で、下ヒゲが非常に短いことがわかります。これは、小さいものから大きいものまでソートされた走行距離データの分布の最初の 75% が非常に集中していることを示しています。上ヒゲ 非常に長い。これは、データ セット全体が広範囲にわたっており、データの最後の 25% の分布が非常に分散していることを示します。このパターンは上の棒グラフにも反映されています。
グラフのプロンプト ポップアップ ボックスを通じて、この箱ひげ図の分布がわかります。Q1 の下位四分位は 3.76、Q3 の上位四分位は 12.27 であるため、IQR 四分位範囲は 8.51 と計算されます。上限と内側の制限は 25.035 です。内側の下限は負なので考慮しません。上限と内側の制限の外側にあるデータは外れ値です。そこで、走行距離の分布をより明確に観察するために、25.035 キロメートルを超える走行距離を異常データとみなし、フィルタリングを行った上で走行距離の分布図を作成することを検討します。
adderLength_data = data[data["adderLength"]<=25.035]["adderLength"].value_counts().sort_index()
adderLength2_c = (
Bar({
"theme": ThemeType.MACARONS})
.add_xaxis([round(i,2) for i in adderLength_data.index.tolist()])
.add_yaxis("骑行距离",adderLength_data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="骑行距离分布图"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
adderLength2_c.render_notebook()
シェア自転車利用者の走行距離(経路)はおおむね2km以内であることがわかる シェア自転車の需要は短距離移動である シェア自転車事業者は、半径内に大規模な交通拠点が無い住宅地もターゲットにできる、住民の娯楽と文化エリアにはシェア自転車が置かれています。
- 各シェア自転車の作業負荷を計測し、不良自転車の可能性が高い自転車を特定
各シェア自転車の比較作業負荷は、シェア自転車の利用回数、シェア自転車の走行時間、シェア自転車の走行距離の3つの観点から計測することができる。
しかし、自転車の使用回数から自転車の作業負荷を測るのは偏りであり、自転車の走行時間という要素が無視されてしまい、何度も使用しても一回の走行時間が短い自転車もあります。自転車の利用回数は少ないですが、毎回の乗車時間は長いです。
自転車の走行時間から各自転車の作業負荷を計測する方が公平に思えますが、鍵の閉め忘れなどによる異常な記録データがクリアされていることが前提となります。利用者が自転車を使用した後、施錠を忘れると10時間「乗車」することになるため、自転車の乗車時間から各シェア自転車の作業負荷を計測した場合、そのような自転車が第1弾となる。取り外し、オーバーホールを行っていますが、作業負荷はそれほど高くありません。
対照的に、各自転車の作業負荷を走行距離から測定する方がより公平であるため、ここではメンテナンスのために持ち出す必要がある自転車を取得するために、走行距離から各自転車の作業負荷を測定することにします。 。
ここでは、作業負荷の基準としてサイクリング時間を使用し、各自転車のサイクリング経路をカウントし、サイクリング経路内の上位 15 台の車両 ID をオーバーホールが必要な自転車のリストとして選択します。フィルタリングされた車両の数も自由に定義できます。
#找出骑行路程前15名的单车
workload_data = data.groupby("bikeid").sum()["adderLength"].sort_values(ascending=False).head(15)
workload_data = pd.DataFrame({
'bikeid':workload_data.index,'adderLength':workload_data.values})
table = Table()
headers = ["bikeid", "adderLength"]
rows = workload_data.values.tolist()
table.add(headers, rows)
table.set_global_opts(
title_opts=ComponentTitleOpts(title="骑行路程前15的共享单车")
)
table.render_notebook()
- 自転車が不足している地域を調べて、新しい自転車を置く場所を決定できるようにする
自転車が不足している地域を見つけるための分析アイデア:
最も注文数の多い8月31日の注文データを計算データとして選択します
- 同じ緯度および経度で移動する自転車の数を計算します。バイク ID で並べ替え、1 つのバイク ID に
複数の注文があり、最も早い期間の注文データを選択し、選択した注文データの開始緯度および経度に対して groupby を 2 回実行します。次に count() を呼び出し、同じ緯度経度の場所で自転車が何台走ったかをカウントします。 - 同じ緯度および経度で移動する自転車の数を計算します。バイク ID で並べ替え、1 つのバイク ID に
複数の注文があり、最新の期間の注文データを選択し、選択した注文データの終了緯度および経度に対して groupby を 2 回実行します。次に、 count( ) を呼び出して、同じ緯度経度で何台の自転車に乗ったかをカウントします。 - 転入者数-転出者数の差を計算し、ある場所の自転車台数の差が0未満の場合、その場所の自転車は「行っては戻らない」ことを意味します。 。紛失数の多い自転車が主に分布しているエリアを数えることにより、新しい自転車の位置を決定します。
自転車が不足している地域を特定するための具体的な分析手順:
元のデータのコピーを小数点第2位までの緯度・経度に変換し、元の緯度・経度との差が0.01度以内の緯度・経度を同じ緯度・経度に分割します(実際の分類)。緯度と経度)、統計の地理的範囲を拡大し、計算を簡素化します。図面が複雑なため、ポイントの蓄積が回避されます。
もちろん、小数点以下何桁まで確保するかは考慮する必要がありますが、小数点以下 2 桁を保持した後の経度と緯度が同じであれば、地球上の実際の経度と緯度の差は 0.01 度を超えることはなく、その差は 0.01 度を超えることはありません。表面間の距離は 1 キロメートルを超えてはなりません。この値が大きすぎる場合 (0.1 度、10 キロメートル)、または小さすぎる場合 (0.001 度、100 メートル) は適切ではありません。
#对经纬度保留两位小数
def change_xy(item):
item["start_location_x"] = round(item["start_location_x"],2)
item["start_location_y"] = round(item["start_location_y"],2)
item["end_location_x"] = round(item["end_location_x"],2)
item["end_location_y"] = round(item["end_location_y"],2)
return item
data = data.apply(change_xy,axis = 1)
注文数が最も多い8月31日の注文データを計算データとして選択し、bicycleidでソートし、1つのbikeridに
複数の注文がある場合は、最も古い期間の注文データを選択し、開始時刻の緯度経度で2回のgroupbyを実行します。選択した注文データのポイントを取得してから、count() を呼び出して、同じ緯度経度で自転車に乗った回数をカウントします。
data_selected = data[data["date"]==31]
#一辆自行车一天骑行最早的记录
early_order = data_selected.groupby(["bikeid"]).apply(lambda t: t[t.start_time==t.start_time.min()])
out_num = early_order.groupby(["start_location_x","start_location_y"]).count()["bikeid"]
選択した 8 月 31 日のデータを bikeid に従って並べ替えます。1 つの bikeid に
複数の注文があり、最新の期間の注文データを選択し、選択した注文データの経度と緯度に対して groupby を 2 回実行してから、count() を呼び出してカウントします。同じ緯度と経度の位置で何台の自転車に乗って戻ってきたか
#一辆自行车一天骑行最晚的记录
last_order = data_selected.groupby(["bikeid"]).apply(lambda t: t[t.start_time==t.start_time.max()])
#每个区块被骑回来多少单车
in_num = last_order.groupby(["end_location_x","end_location_y"]).count()["bikeid"]
in_num
データを結合して DataFrame (negative_block_data) を取得し、結合後に表示される空の値を 0 で埋め、追い出した自転車の台数と戻ってきた自転車の台数の差を計算し、計算結果を新しい列「ギャップ」に表示し、出発して戻ってこない自転車の台数が 5 台に達するブロック データを選別します。
block_out_in_df = pd.DataFrame({
"out":out_num,"in":in_num})
block_out_in_df.fillna(0,inplace=True)
block_out_in_df["gap"] = block_out_in_df.apply(lambda item: item["in"]-item["out"],axis = 1)
#找出有去无回单车数量达-5辆的经纬度地区
negative_block_data = block_out_in_df[(block_out_in_df["gap"]) < -4].sort_values(by=["gap"])
現在の問題は、negative_block_data には緯度と経度のみがあり、地理的位置が含まれていないことです。緯度と経度を詳細な地理的位置に変換するには、「リバース ジオコーディング」を実行する必要があります。このステップでは、変換に Gaode マップの API を使用することを選択しますが、API を使用する前に、Gaode アカウントを登録してキー (キー) を申請する必要があります。
Gaode オープン プラットフォーム: https://lbs.amap.com/api/webservice/summary、コンソールに入ります -> アプリケーション管理 -> 私のアプリケーション -> 新しいアプリケーションを作成します ->…。固有のキーが得られます。GaodeマップのAPIを呼び出す際には、このキーを添付する必要があります。
APIのURL:https://restapi.amap.com/v3/geocode/regeo、APIからget形式でデータを取得し、経度緯度情報とキーなどのリクエストパラメータをパラメータに入れて送信します。一緒に。
def parse_loc(location):
location_str=str(location[0])+','+str(location[1])
parameters = {
'output': 'json', 'location': location_str, 'key': '42e2546ff2e9a3ac3aa3c001eaebe6ea','extensions':'all'}
base = 'https://restapi.amap.com/v3/geocode/regeo'
response = requests.get(base, parameters)
answer = response.json()["regeocode"]["formatted_address"]
return answer
location = []
for i in negative_block_data.index:
item_result = parse_loc(i)
location.append(item_result)
negative_block_data["location"] = location
#新单车投放地点名单
print(negative_block_data)
#保存成csv文件
negative_block_data.to_csv("./negative_block_data.csv")
自転車不足地域一覧では、地理的位置を追加して集計した自転車不足地域の一覧(一部)を掲載しています リストデータに含まれるデータは、経度、緯度、特定の地域の自転車の総走行台数です。場所、特定の場所の自転車の総数、特定の場所のデータ 自転車の値と場所の名前が欠落しています。
自転車不足地域のリストを整理するには、地図上で自転車不足地域を区切って地理的散布図を描画する必要がありますが、このステップでは folium を使用します。Folium は、地図、ドット、円、カラー マーカーを地図上に描画するために使用される Python ツール クラスです。folium を使用すると、美しくダイナミックでインタラクティブな地図を作成できます。Folium は、マウスをクリックしたときに表示されるコンテンツを表示するなどのインタラクションもサポートしています。
folium モジュール コマンドをインストールします。
import folium
#数据导入
import pandas as pd
data = pd.read_csv("negative_block_data.csv", encoding='GBK')
print(data)
Folium はデフォルトで世界地図を描画します。座標 (緯度と経度) を指定して地図の中心点を初期化することもできます。上海の Mobike データの分析用であるため、上海の中心ランドマーク [31.233705, 121.471632] はここで指定します。
#创建地图对象
import folium
from folium import plugins
shanghai_map = folium.Map(location=[31.233705, 121.471632])
folium.Marker([31.233705, 121.471632], popup='<p style="color: green">上海人民广场</p>',icon=folium.Icon(icon='cloud',color='green')).add_to(shanghai_map)
marker_cluster = plugins.MarkerCluster().add_to(shanghai_map)
#互动效果-弹出提示popup,实例中,点击一个点,就会弹出当前点击位置的缺少单车数量以及地点名。
for name,row in data.iterrows():
folium.Marker([row[1], row[0]], popup=folium.Popup('<b>单车数量:</b>{0}<br><b>地点:</b>{1}'.format(int(row["gap"]), row["location"]),max_width=1000),
tooltip='点击查看数据').add_to(marker_cluster)
#保存到本地并用浏览器打开
shanghai_map.save('lackOfBike.html')
知らせ:
- Folium の地理的位置散布図の方が表示に適していますが、読み込みのスムーズさを考慮すると、あまり大きなデータを読み込むことはお勧めできません。
- コンポーネントは外部ネットワークから jquery を読み込むため、配置されているネットワークが Google にアクセスできない場合、効果が表示されない可能性があります。解決策は、生成された HTML ファイル内の jquery の cdn アドレスを国内のミラー イメージに置き換えることです。
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
と置換する
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
- 自転車の集中・不足地域を把握し、自転車の配車(搬出・搬入)場所を決定
以上で自転車不足地域の集計が完了しましたので、次に空き自転車が存在する地域の集計を行っていきますが、統計上の考え方は上記の自転車不足地域と基本的に同じですので、把握することができます。新しい自転車の場所。
#找出有空闲单车数量达5辆的经纬度地区
positive_block_data = block_out_in_df[(block_out_in_df["gap"]) > 4].sort_values(by=["gap"])
#获取经纬度地区对应的地理位置
location2 = []
for i in positive_block_data.index:
item_result = parse_loc(i)
location2.append(item_result)
positive_block_data["location"] = location2
#保存数据
positive_block_data.to_csv("./positive_block_data.csv")
無料の自転車が集まっているエリアのリスト (一部) が図に示されています。
次に、地図上で自転車の不足しているエリアと無料のエリアを区切り、さらに Folium を使用して地理的位置散布図を描画する必要があります。
import pandas as pd
import folium
from folium import plugins
#导入数据
data_negative = pd.read_csv('negative_block_data.csv')
data_positive = pd.read_csv('positive_block_data.csv')
m = folium.Map(location=[31.233705, 121.471632])
folium.Marker([31.233705, 121.471632], popup='<p style="color: green;width:100px;height:50px;font-size:20px" >上海中心点</p>',icon=folium.Icon(icon='cloud',color='blue')).add_to(m)
marker_cluster = plugins.MarkerCluster().add_to(m)
#缺少单车的地点,数量绘制坐标点
for i in range(0,len(data_negative.iloc[:,0])):
x=data_negative.iloc[:,1][i]
y=data_negative.iloc[:,0][i]
test = folium.Html("<b>缺少的单车:{}</b></br><p>地点:{}</p>".format(int(data_negative["gap"][i]),data_negative["location"][i]),script=True)
popup = folium.Popup(test, max_width=1000)
folium.Marker([x,y],popup=popup,icon=folium.Icon(color='red')).add_to(m)
#富余单车的地点,数量和绘制坐标点
for i in range(0,len(data_positive.iloc[:,0])):
x=data_positive.iloc[:,1][i]
y=data_positive.iloc[:,0][i]
test = folium.Html(
"<b>富余的单车:{}</b></br><p>地点:{}</p>".format(int(data_positive["gap"][i]),data_positive["location"][i]),
script=True)
popup = folium.Popup(test, max_width=1000)
folium.Marker([x,y],popup=popup,icon=folium.Icon(color='green')).add_to(m)
m.save('shanghai_map.html')
赤の点:Mobike シェア自転車が不足している場所、緑の点:Mobike シェア自転車が余っている場所。
このチャートから自転車が不足している場所と余っている場所を把握することができ、自転車が余っている場所から自転車が無い場所へ自転車を配車することができます。
ユーザーディメンション
RFM モデルでは、ユーザーを階層化し、8 つの次元に分けたユーザーに対応するユーザー特性を図に示します。ユーザーの種類
「user_class」をカウントし、各タイプのユーザー数をカウントし、棒グラフが描かれます。
user_class_data = data.loc[:,["userid","user_class"]].drop_duplicates()
user_class_num = user_class_data["user_class"].value_counts()
user_c = (
Bar({
"theme": ThemeType.MACARONS})
.add_xaxis(user_class_num.index.tolist())
.add_yaxis("各类型用户的数量",user_class_num.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="各类型用户的数量"))
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
user_c.render_notebook()
図にあるように、新規利用者数は第2位であり、第1位とほぼ同数となっており、シェア自転車Mobikeの実績が上昇傾向にあることが分かります。新規ユーザーとのつながりが彼らを忠実なユーザーに変え、失ったユーザーの数は 3 番目に多く、またユーザーのリコール戦略をどのように策定するかが Mobike の前のハードルとなっています。
深耕を頻繁に行うユーザー、高消費量のリコールを行うユーザー、重要な価値の損失を警告するユーザーの数は非常に少なく、これは Mobike が 2016 年の時点でまだ新しい企業であり、そのユーザー構造が完全ではないことを示しています。関係公的機関と連携し、ロードバイクマラソン大会の開催、大会スポンサー、自転車競技車両の提供等を行い、より多くの参加者によるMobikeの利用促進、Mobikeのブランド認知の向上、ユーザー構造の安定化を図ることができます。
各ユーザーの平均消費額は以下の
通り 図を見ると、重要価値ユーザーの平均消費額は1位だが、ユーザー数は4位にとどまっており、上位との差はまだあることがわかる。 2. マーケティング戦略を講じる必要がある このユーザーを増やす このタイプのユーザーの特徴は、購入頻度が高く、消費頻度が高く、シェア自転車の需要が高いことを示しています。人口密集地や住宅地が密集する工業地帯やその他の企業において、潜在消費者の拡大を図るため、特定の地域に自転車を投入する。