利用SQL和Python分別實現人流量查詢,考驗邏輯思維的時候到了
本來這篇是要寫Python的可視化的,但無意中發現了一道題目,發現通過這道題可以很好地鍛煉一下邏輯思維能力,而且也可以復習下SQL和Python的編寫,于是便決定先寫這篇了。
通過這道題我們會發現,其實在分析工作中,最重要的能力是邏輯思維,程序只不過是實現邏輯的工具,沒有邏輯思維能力,程序就是無本之源。而且,雖然實現一個結果會有多種邏輯,但好的邏輯會讓我們的程序更具簡潔性、可觀性、高效性。
下面是結合自身理解所總結的兩類實現邏輯,我相信肯定還會有更優秀的邏輯在某些大牛的腦中!
案例介紹
案例來源于LeetCode,這樣的需求在時間序列數據中還是較為常見的。
某市體育館每日人流量信息被記錄在stadium表的三列信息中:序號 (id)、日期 (visit_date)、 人流量 (people),找出至少連續三行人流量不少于100的記錄。

思路分析
最簡單的思路肯定是對stadium表進行三次笛卡爾積連接,但這種方式在數據量大時不可取,而且也不具備泛化性(譬如需求改成至少連續十行)。網上也流傳著阿里的編程規范——禁止三表以上的連接。
總之,這種思路不是我們該采取的,我們需要尋找其它思路。
(1)構建等差數列

從上圖中我們能發現一個規律,滿足條件的數據區域在原始表和結果表中的行編號均是等差數列,兩個等差數列的差值是固定的。譬如,數列A1和B1的差值均為1;數列A2和B2的差值均為2。
只要我們保證每塊區域等差數列的差值各不相等,那我們就可以通過篩選差值出現的次數來篩選滿足條件的區域。例如,差值2出現了4次,滿足條件,那該差值對應的記錄就是我們需要的數據。
構建差值的方式除了通過行編號外,也還有其它方式,大家可以想一想。
(2)數據切片

從圖中可看出,if_true是輔助列,表示是否滿足條件,1為True,0為False。我們要選擇滿足條件的區域,可通過用0對該列進行切片,得到的是全為1的不同長度的小數列,根據每個小數列的長度來篩選滿足條件的區域。
在圖中就是得到了長度為a和b的數列,通過計算數列的長度來找出滿足條件的區域。
程序實現
上節我們選擇了兩種思路,其中Python兩種思路都可以實現,SQL可實現第一種思路。本節用SQL實現第一種思路,用Python實現第二種思路。
(1)SQL
- select id,visit_date,people
- from
- (select t2.*,count(1) over(partition by rn2) rn3
- from
- (selectt1.*,rn1 - row_number() over(order by visit_date) rn2
- from
- (select *,row_number() over() as rn1
- from stadium order by visit_date)t1 #t1表對日期升序排列后生成行編號
- where people>=100) t2 #t2表篩選人數不低于100的數據,并用原行編號減去新生成的行編號得到差值
- where 1=1) t3 #t3表統計每類差值出現的次數
- where rn3>2 #篩選次數大于2的數據即為所需要的數據
因為實際中表中的ID幾乎都不是連續的數字,所以為了保證泛化性就先生成了行編號,這樣就不用依賴于ID了。
除此之外也還可以通過用戶變量等方式實現,大家可以試著想一想。
(2)Python
- import pandas as pd
- dt=pd.DataFrame({"id":range(1,9),
- "visit_date":pd.date_range(start="2017-01-01",periods=8),
- "people":[10,109,150,99,145,1455,199,188]})
- dt["col1"]=dt["people"].apply(lambda x : 1 if x>=100 else 0)
- #生成人數是否不低于100的新列
- dt['counter'] = (dt["col1"]==0).cumsum()
- #按照col1列是否為0計算累計和,標記每個連續區域
- dt = dt[dt["col1"] !=0]
- #剔除人數低于100的記錄
- gb=dt.groupby("counter")["id"].count()
- # 統計各標記值的次數
- result=dt[dt["counter"].isin(gb[gb>2].index)]
- #篩選滿足條件的數據
這里有一點需要注意,如果直接將col1列轉為字符串按0進行切片的話,雖然可以求出滿足條件的區域數量和長度,但很難再尋找到具體的區域。
- split_col1="".join([str(i) for i in dt["col1"]]).split("0")
原本是按照的這種思路,但發現尋找長度符合字符串在原列表中的索引時會比較麻煩,尤其是當需要查找多個索引值時。
但此種思路還是非常重要,因為在只是計算連續區域的最大值時會非常簡單。
結語
以上只是兩種簡單的邏輯,其實還有一些邏輯方法,但其本質大都差不多,本文就不一一列舉了。至于是否還有更高效的邏輯方法,就等著大牛們來指導吧。
如果有完整看完的朋友就會發現,一個簡單的例子就可以有多種實現方法,在將每種方法都自己寫一遍的過程中,就是對已有知識的一種梳理和復習。