上海市地鐵刷卡數據到OD矩陣

前言

在這裏插入圖片描述
上期 ,定義出早高峯和晚高峯時段,接下來就是要分早高峯和晚高峯來做出上海市地鐵刷卡人次OD矩陣(origin-destination matrix),因爲上海軌道交通具有很好的連通性,所以我們可以利用抽象的矩陣理論來分析。這樣整個上海市軌道交通的通勤OD情況就能夠通過一個矩陣來表示和研究,矩陣中的元用來刻畫各站點的客流來源和去向。

思路

首先要定義出地鐵OD矩陣和矩陣中的元,定義
A=(aij)A=(a_{ij})
爲上海市地鐵OD矩陣,矩陣中的元aija_{ij}定義爲同一時段從第jj個站到第ii個站的刷卡人次,比如令ii爲人民廣場站,jj爲富錦路,此時aija_{ij}就表示相同時段內從富錦路進站,從人民廣場出站的那波人,令jj取不同時值時候,同樣也可以知道其他站點到人民廣場站的刷卡人次,同理也可以令ii爲其他站點,這樣令iijj取不同的值時,就知道不同站點進出和特定流向情況,現在的問題是如何度量aija_{ij}大小。這裏給出集合論方法,令
A={:j}A=\{卡號: j進站的卡號\}
B={i}B=\{卡號:i出站的卡號\}
因爲一個卡號對應一個人,集合AA表示從jj站進來的那波人次,集合BB表示從ii站出來的那波人次,如果兩撥人次重複的,那麼重複的那小波人次就是第jj個站到第ii個站的刷卡人次 則,此時集合AA和集合BB的交集ABA\cap B 表示從 jj 進站的且從 ii 出站的卡號, 即交集ABA\cap B的勢便是aija_{ij}的取值。

結果預覽

在這裏插入圖片描述

代碼

# -*- coding: utf-8 -*-
"""
project_name:read_mysql
@author: 帥帥de三叔
Created on Thu Dec 12 15:10:32 2019
"""
import numpy as np #導入數值分析模塊
import pandas as pd #導入數據分析模塊
from sqlalchemy import create_engine #數據庫引擎
connection=create_engine("mysql+pymysql://root:123456@localhost:3306/metro_sh?charset=utf8") #連接數據庫
sql=pd.read_sql('zaogaofeng',connection) #讀取sql數據庫
df=pd.DataFrame(sql) #數據框化
#print(df.head()) #測試表頭前5
stations=df['站點'].unique() #所有去重的站點
M=np.zeros(shape=(313,313)) #構造一個313*313零矩陣

for index1,i in enumerate(stations): #行
    for index2,j in enumerate(stations): #列
        card_out_i=df[(df['站點']==i)&(df['費用']!=0)]['卡號'] #第i站出站卡號序列
        card_in_j=df[(df['站點']==j)&(df['費用']==0)]['卡號'] #第j站進站卡號
        print(len(card_out_i),len(card_in_j)) #測試j進站,i出站的卡號
        ai=set(card_out_i) #出站列表集合化
        aj=set(card_in_j)  #進站列表集合化
        aij=ai.intersection(aj) #求交集,即從j進站,i出站的卡號
        print(len(aij)) #交集計數
        count=len(aij) 
        M[index1,index2]=count #賦值               
M=pd.DataFrame(M) #數據框化
M.columns=stations #構造表頭
M.index=stations #構造索引
M.to_excel("早高峯OD矩陣.xlsx") #寫入excel     

代碼解讀

整段代碼大體過程是先用sqlalchemy模塊的create_engine類連接到MySQL數據庫,緊接着用 pd.read_sql() 讀取庫裏面的數據表,比如這裏的zaogaofeng,數據讀出來了,接下來就完全是python操作了,如提取不重複的站點,用 card_out_i=df[(df[‘站點’]==i)&(df[‘費用’]!=0)][‘卡號’] 篩出第 ii 站出站卡號序列,用 card_in_j=df[(df[‘站點’]==j)&(df[‘費用’]==0)][‘卡號’] 篩出第 jj 站出站卡號序列,然後用set() 函數集合化,並求交集和交集的勢,並把所求的結果賦值給先定義的零矩陣M的第ii行第jj列元aija_{ij},最後把重新賦值後的矩陣寫入到excel便得到想要得OD矩陣。

改進思路

首先,代碼運行太慢了,不管是讀取數據庫MySQL還是構造OD矩陣中的多兩層循環還是集合篩選運算,都很費時;其次是代碼寫的太散,通用性不強,想打包成函數或者類。下面以晚高峯爲例,利用面向對象編程和模塊化思想,看看代碼是不是簡潔些,運行時間多少?

改進代碼

# -*- coding: utf-8 -*-
"""
project_name:read_mysql
@author: 帥帥de三叔
Created on Thu Dec 12 15:10:32 2019
"""
import time 
import numpy as np #導入數值分析模塊
import pandas as pd #導入數據分析模塊
from sqlalchemy import create_engine #數據庫引擎
connection=create_engine("mysql+pymysql://root:123456@localhost:3306/metro_sh?charset=utf8") #連接數據庫
sql=pd.read_sql('wangaofeng',connection) #讀取sql數據庫
data=pd.DataFrame(sql) #數據框化
print(data.head()) #測試表頭前5

def generate_od_matrix(df): #自定義構造OD矩陣函數
    stations=list(df['站點'].unique()) #所有去重的站點,object類型轉list
    OD_Matrix=np.zeros(shape=(len(stations),len(stations))) #構造一個313*313零矩陣
    for index1,i in enumerate(stations): #行
        for index2,j in enumerate(stations): #列
            print(i,j)
            card_out_i=df[(df['站點']==i)&(df['費用']!=0)]['卡號'] #第i站出站卡號序列
            card_in_j=df[(df['站點']==j)&(df['費用']==0)]['卡號'] #第j站進站卡號
            print(len(card_out_i),len(card_in_j)) #測試j進站,i出站的卡號
            ai=set(card_out_i) #出站列表集合化
            aj=set(card_in_j)  #進站列表集合化
            aij=ai.intersection(aj) #求交集,即從j進站,i出站的卡號
            print(len(aij)) #交集計數
            count=len(aij) #統計j進站,i出站的刷卡人次
            OD_Matrix[index1,index2]=count #賦值
    M=pd.DataFrame(OD_Matrix) #數據框化
    M.columns=stations #構造表頭
    M.index=stations #構造索引
    M.to_excel("晚高峯OD矩陣.xlsx") #寫入excel  
    
if __name__=="__main__": #起始主函數
    start_time=time.time() #開始時間
    generate_od_matrix(data)
    end_time=time.time() #結束時間
    print("the process lasts:",end_time-start_time) #程序運行總時間   

代碼解讀

整個程序跑了20989秒,近6個小時,要知道這纔是晚高峯80分鐘的時間跨度,150萬條刷卡記錄,如果換成一天的就是900萬條,大約35小時,關鍵怎麼在電腦上快速讀取這麼大數據和處理,這就是接下來要研究的問題了。爲此,從新整理了代碼,寫了兩個函數,第一個函數 read_mysql 用來讀取MySQL得到一個數據框,第二個函數 generate_od_matrix調用第一個函數的結果來生成OD矩陣,最後主函數用來保存OD矩陣到excel中,本機配置如下
在這裏插入圖片描述
程序是從週一下午4點左右開跑的,週三早上來上班,發現程序跑完了,甚是欣慰,總共花了137036.9512345791秒,大約是38小時,比計劃中的多3小時,這也是可以理解的,其實讀數據大約只要30分鐘,大部分時間是花在遍歷數據並集合化處理上面。

完整代碼

# -*- coding: utf-8 -*-
"""
project_name:read_ten_million_rows_data_from_mysql
@author: 帥帥de三叔
Created on Fri Dec 20 13:19:18 2019
"""
import time #導入時間模塊
import numpy as np #導入數值分析模塊
import pandas as pd #導入數據分析模塊
import pymysql #導入數據庫連接模塊 

def read_mysql(): #定義讀取MySQL函數
    rows=[] #用來存放行數據
    db=pymysql.connect(host='localhost',user="root",passwd="123456",database="metro_sh",port=3306,charset='utf8',cursorclass =pymysql.cursors.SSCursor) #連接到本地MySQL數據庫
    cursor=db.cursor() #獲取遊標
    cursor.execute("SELECT * FROM metro20160901") #篩取數據 
    while True:
        row=cursor.fetchone() #一次只取一行
        rows.append(row)
        print("正在讀取第%d行"%len(rows))
        print(row)
        if not row:
            break
    cursor.close() #關閉遊標
    db.close() #關閉數據庫連接
    df=pd.DataFrame(rows)
    return df

def generate_od_matrix(df): #定義生成od矩陣的函數
    df.columns=["卡號","日期","時間","站點","方式","費用","是否有優惠"] #重命名錶頭
    stations=df['站點'].unique() #所有去重的站點 
    od_matrix=np.zeros(shape=(len(stations),len(stations))) #構造一個313*313零矩陣
    for index1,i in enumerate(stations): #行
        for index2,j in enumerate(stations): #列
            print(index1,index2)
            card_out_i=df[(df['站點']==i)&(df['費用']!=0)]['卡號'] #第i站出站卡號序列
            card_in_j=df[(df['站點']==j)&(df['費用']==0)]['卡號'] #第j站進站卡號
            print(len(card_out_i),len(card_in_j)) #測試j進站,i出站的卡號
            ai=set(card_out_i) #出站列表集合化
            aj=set(card_in_j)  #進站列表集合化
            aij=ai.intersection(aj) #求交集,即從j進站,i出站的卡號
            print(len(aij)) #交集計數
            count=len(aij) 
            od_matrix[index1,index2]=count #賦值               
    od_matrix=pd.DataFrame(od_matrix) #數據框化   
    return od_matrix,stations

if __name__=="__main__":
    start_time=time.time() #開始時間
    M,stations=generate_od_matrix(read_mysql()) #函數嵌套調用讀取數據庫函數
    M.columns=stations #構造表頭
    M.index=stations #構造索引
    M.to_excel("上海市OD矩陣.xlsx") #寫入excel  
    end_time=time.time() #開始時間
    print('程序耗時:',end_time-start_time) #測試讀取數據時間

如果你不會寫代碼或直接只想要數據的話可以關注“三行科創”公衆號,在對話框留個郵箱和所要數據名稱,我發給你。

參考文獻

1,https://wenku.baidu.com/view/165abf1d336c1eb91a375d8d.html
2,https://wenku.baidu.com/view/fa71f2107375a417866f8f81.html?sxts=1575956307792
3,https://wenku.baidu.com/view/5710cba20d22590102020740be1e650e52eacf23.html?rec_flag=default&sxts=1575957393812

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章