一場pandas與SQL的巔峯大戰(三)

點擊上方「超哥的雜貨鋪」,輕鬆關注
php


本文目錄以下:
python

在前兩篇文章中,咱們從多個角度,由淺入深,對比了pandas和SQL在數據處理方面常見的一些操做。web


具體來說,第一篇文章一場pandas與SQL的巔峯大戰涉及到數據查看去重計數條件選擇合併鏈接分組排序等操做。sql


第二篇文章一場pandas與SQL的巔峯大戰(二)涉及字符串處理窗口函數行列轉換類型轉換等操做。您能夠點擊往期連接進行閱讀回顧。微信


在平常工做中,咱們常常會與日期類型打交道,會在不一樣的日期格式之間轉來轉去。app


本文依然沿着前兩篇文章的思路,對pandas和SQL中的日期操做進行總結,其中SQL採用Hive SQL+MySQL兩種方式,內容與前兩篇相對獨立又彼此互爲補充。一塊兒開始學習吧!less

◆ ◆ ◆  ◆ 函數

數據概況學習

數據方面,咱們依然採用前面文章的訂單數據,樣例以下。在正式開始學習以前,咱們須要把數據加載到dataframe和數據表中。本文的數據、代碼以及清晰的PDF版本能夠在公衆號後臺回覆「對比三」獲取哦~ui


pandas加載數據


import pandas as pd
data = pd.read_excel('order.xlsx')
#data2 = pd.read_excel('order.xlsx', parse_dates=['ts'])
data.head()
data.dtypes


須要指出,pandas讀取數據對於日期類型有特殊的支持。


不管是在read_csv中仍是在read_excel中,都有parse_dates參數,能夠把數據集中的一列或多列轉成pandas中的日期格式。


上面代碼中的data是使用默認的參數讀取的,在data.dtypes的結果中ts列是datetime64[ns]格式,而data2是顯式指定了ts爲日期列,所以data2的ts類型也是datetime[ns]


若是在使用默認方法讀取時,日期列沒有成功轉換,就可使用相似data2這樣顯式指定的方式。

MySQL加載數據


我準備了一個sql文件t_order.sql,推薦使用navicate客戶端,按照上圖所示方式,直接導入便可。

Hive加載數據


create table `t_order`(
`id` int,
`ts` string,
`uid` string,
`orderid` string,
`amount` float
)
row format delimited fields terminated by ','
stored as textfile;

load data local inpath 't_order.csv' overwrite into table t_order;
select * from t_order limit 20;


在hive中加載數據咱們須要先創建表,而後把文本文件中的數據load到表中,結果以下圖所示。



咱們在MySQL和Hive中都把時間存儲成字符串,這在工做中比較常見,使用起來也比較靈活和習慣,所以沒有使用專門的日期類型。

開始學習


咱們把日期相關的操做分爲日期獲取日期轉換日期計算三類。下面開始逐一學習。

日期獲取



1.獲取當前日期,年月日時分秒


pandas中可使用now()函數獲取當前時間,但須要再進行一次格式化操做來調整顯示的格式。咱們在數據集上新加一列當前時間的操做以下:



MySQL有多個函數能夠獲取當前時間:

now(),current_timestamp,current_timestamp(),sysdate(),localtime(),localtime,localtimestamp,localtimestamp()等。


點擊圖片查看大圖


hive中獲取當前時間,可使用 current_timestamp(), current_timestamp,獲得的是帶有毫秒的,若是想保持和上面一樣的格式,須要使用substr截取一下。以下圖所示:


圖中代碼:

#pandas
data['current_dt'] = pd.datetime.now()
data['current_dt'] = data['current_dt'].apply(lambda x : x.strftime('%Y-%m-%d %H:%M:%S'))
data.head()
#也能夠data['current_dt'] = pd.datetime.now().strftime('%Y-%m-%d %H:%M:%S')一步到位

#MySQL 
SELECT *, now(),current_timestamp(),current_timestamp
FROM `t_order`;
SELECT *, sysdate(),ocaltime(),localtime
FROM `t_order`;
SELECT *, localtimestamplocaltimestamp()
FROM `t_order`;

#HiveQL
select *, substr(current_timestamp119), substr(current_timestamp(), 119
from t_order limit 20;


2.獲取當前時間,年月日


pandas中彷佛沒有直接獲取當前日期的方法,咱們沿用上一小節中思路,進行格式轉換獲得當前日期。固然這不表明python中的其餘模塊不能實現,有興趣的朋友能夠本身查閱相關文檔。



MySQL中能夠直接獲取當前日期,使用curdate()便可,hive中也有相對應的函數:current_date()。




圖片中的代碼:

#pandas
data['dt_date'] = pd.datetime.now().strftime('%Y-%m-%d')
data.head()

#MySQL
SELECT *, curdate() FROM `t_order`;

#HiveQL
select *, current_date() from t_order limit 20;


3.提取日期中的相關信息


日期中包含有年月日時分秒,咱們能夠用相應的函數進行分別提取。下面咱們提取一下ts字段中的天,時間,年,月,日,時,分,秒信息。



在MySQL和Hive中,因爲ts字段是字符串格式存儲的,咱們只需使用字符串截取函數便可。二者的代碼是同樣的,只須要注意截取的位置和長度便可,效果以下:




圖片中代碼:

#pandas
data['dt_day'] = data['ts'].dt.date#提取年月日
data['year'] = data['ts'].dt.year#提取年份
data['month'] = data['ts'].dt.month#提取月份
data['day'] = data['ts'].dt.day#提取天數
data['dt_time'] = data['ts'].dt.time#提取時間
data['hour'] = data['ts'].dt.hour#提取小時
data['minute'] = data['ts'].dt.minute#提取分鐘
data['second'] = data['ts'].dt.second#提取秒
data.head()

#MySQL
select ts, substr(ts, 110), substr(ts, 14), substr(ts, 62), 
substr(ts, 92), substr(ts, 128), substr(ts, 122), 
substr(ts, 152), substr(ts, 182)
from t_order;

#HiveQL
select ts, substr(ts, 110), substr(ts, 14), substr(ts, 62), 
substr(ts, 92), substr(ts, 128), substr(ts, 122), 
substr(ts, 152), substr(ts, 182)
from t_order limit 20;


日期轉換


1.可讀日期轉換爲unix時間戳


在pandas中,我找到的方法是先將datetime64[ns]轉換爲字符串,再調用time模塊來實現,代碼以下:



能夠驗證最後一列的十位數字就是ts的時間戳形式。

ps.在此以前,我嘗試了另一種藉助numpy的方式,進行類型的轉換,但轉出來結果不正確,比指望的結果多8個小時,我寫在這裏,歡迎有經驗的讀者指正。

import numpy as np
data['ts_timestamp'] = (data.ts.astype(np.int64)/1e9).astype(np.int64)
data.head()
#獲得的ts_timestamp結果
#1564650940  1564653606  1564653875等恰好比正確的結果多8個小時


MySQL和Hive中可使用時間戳轉換函數進行這項操做,其中MySQL獲得的是小數形式,須要進行一下類型轉換,Hive不須要。




圖中代碼:

#python
def transfer_time_format(x):
    import time
    tmp_time 
= time.strptime(x, '%Y-%m-%d %H:%M:%S')
    res_time = int(time.mktime(tmp_time))
    return res_time

data['str_ts'] = data['ts'].dt.strftime('%Y-%m-%d %H:%M:%S')
data['str_timestamp'] = data['str_ts'].apply(transfer_time_format)
data.head()
#使用匿名函數的寫法
#data['str_timestamp'] = data['str_ts'].apply(lambda x: int(time.mktime(time.strptime(x, '%Y-%m-%d %H:%M:%S'))))

#MySQL
select *, cast(unix_timestamp(ts) as int)
from t_order;

#Hive
select *, unix_timestamp(ts) from t_order limit 20;


2.unix時間戳轉換爲可讀日期


這一操做爲上一小節的逆向操做。

在pandas中,咱們看一下如何將str_timestamp列轉換爲原來的ts列。這裏依然採用time模塊中的方法來實現。


ps.你可能發現了上面代碼中有一列是ori_dt,雖然看上去是正確的,但格式多少有那麼點奇怪,這也是我在學習過程當中看到的一個不那麼正確的寫法,貼出來供你們思考。

data['ori_dt'] = pd.to_datetime(data['str_timestamp'].values, unit='s', utc=True).tz_convert('Asia/Shanghai')
data.head()
#使用默認的pd.to_datetime並不能轉會正確的時間,比實際時間小8個小時
#在網上看到了這種寫法能把8個小時加回來,但顯示的很奇怪。

回到MySQL和Hive,依然只是用一個函數就解決了。


圖中代碼以下:

#pandas:
def transfer_time_format2(x):
    import time
    time_local = time.localtime(x)
    res_time = time.strftime('%Y-%m-%d %H:%M:%S', time_local)
    return res_time
data['ori_ts'] = data['str_timestamp'].apply(transfer_time_format2)
data.head()

#MySQL
select *, from_unixtime(cast(unix_timestamp(ts) as int))
from t_order;

#Hive
select *, from_unixtime(unix_timestamp(ts)) from t_order limit 20;


3.10位日期轉8位


對於初始是ts列這樣年月日時分秒的形式,咱們一般須要先轉換爲10位年月日的格式,再把中間的橫槓替換掉,就能夠獲得8位的日期了。

因爲打算使用字符串替換,咱們先要將ts轉換爲字符串的形式,在前面的轉換中,咱們生成了一列str_ts,該列的數據類型是object,至關於字符串,能夠在此基礎上進行這裏的轉換。

MySQL和Hive中也是一樣的套路,截取和替換幾乎是最簡便的方法了。



圖中代碼:

#pandas
data['str_ts_8'] = data['str_ts'].astype(str).str[:10].apply(lambda x: x.replace('-',''))
data.head()

#MySQL
select replace(substr(ts, 110), '-'''
from t_order;

#Hive
select *, regexp_replace(substr(ts, 110),'-','')
from t_order limit 20;


固然,咱們也有另外的解法:使用先將字符串轉爲unix時間戳的形式,再格式化爲8位的日期。




圖中代碼

#MySQL
select *, from_unixtime(cast(unix_timestamp(ts) as int), '%Y%M%d')
from t_order;

#Hive
select *, from_unixtime(unix_timestamp(ts),'yyyyMMdd'from t_order limit 20;


pandas中咱們也能夠直接在unix時間戳的基礎上進行操做,轉爲8位的日期。具體作法只要上面的transfer_time_format2函數便可,效果以下圖所示。



def transfer_time_format3(x):
    import time
    time_local = time.localtime(x)
    res_time = time.strftime('%Y%m%d', time_local)#改這裏的格式就好
    return res_time
data['str_ts_8_2'] = data['str_timestamp'].apply(transfer_time_format3)
data.head()


4.8位日期轉10位


這一操做一樣爲上一小節的逆向操做。

結合上一小節,實現10位轉8位,咱們至少有兩種思路。能夠進行先截取後拼接,把橫線-拼接在日期之間便可。二是藉助於unix時間戳進行中轉。SQL中兩種方法都很容易實現,在pandas咱們還有另外的方式。

方法一:

pandas中的拼接也是須要轉化爲字符串進行。以下:

MySQL和Hive中,可使用concat函數進行拼接:



圖中代碼以下:

#python
data['str_ts_10'] = data['str_ts_8'].apply(lambda x : x[:4] + "-" + x[4:6] + "-" + x[6:])
data.head()

#MySQL
select id, ts, concat(substr(dt8, 14), '-'substr(dt8, 52), '-'substr(dt8, 7,2))
from
(
select *,  replace(substr(ts, 110), '-''')  as dt8
from t_order
) a 

#Hive
select id, ts, concat(substr(dt8, 14), '-'substr(dt8, 52), '-'substr(dt8, 7,2))
from
(
select *, regexp_replace(substr(ts, 110),'-',''as dt8
from t_order
) a 
limit 20;


方法二,經過unix時間戳轉換:

在pandas中,藉助unix時間戳轉換並不方便,咱們可使用datetime模塊的格式化函數來實現,以下所示。

Mysql和Hive中unix_timestamp接收的參數不同,前者必須輸入爲整數,後者能夠爲字符串。咱們的目標是輸入一個8位的時間字符串,輸出一個10位的時間字符串。因爲原始數據集中沒有8位時間,咱們臨時構造了一個。代碼以下:



圖中代碼以下:

#pandas
def transfer_time_format4(x):
    from datetime import datetime
    tmp_time = datetime.strptime('20190801', '%Y%m%d')
    res_time = datetime.strftime(tmp_time, '%Y-%m-%d')
    return res_time
data['str_ts_10_2'] = data['str_ts_8'].apply(transfer_time_format4)
data.head()

#MySQL
select *, 
replace(substr(ts, 110),'-'''),
from_unixtime(unix_timestamp(cast(replace(substr(ts, 110),'-''')as int)),'%Y-%m-%d')
from t_order
;

#Hive
select *, 
regexp_replace(substr(ts, 110),'-'''),
from_unixtime(unix_timestamp(regexp_replace(substr(ts, 110),'-'''), 'yyyyMMdd'),'yyyy-MM-dd')
from t_order
limit 20
;


ps.關於時間Hive中的時間轉換,我在以前總結Hive函數的文章的最後一部分中已經有過梳理,例子比此處更加具體,歡迎翻閱經常使用Hive函數的學習和總結

日期計算


日期計算主要包括日期間隔(加減一個數變爲另外一個日期)和計算兩個日期之間的差值。

1.日期間隔


pandas中對於日期間隔的計算須要藉助datetime 模塊。咱們來看一下如何計算ts以後5天和以前3天。


使用timedelta函數既能夠實現天爲單位的日期間隔,也能夠按周,分鐘,秒等進行計算。

在MySQL和Hive中有相應的日期間隔函數date_add,date_sub函數,但使用的格式略有差別。


須要注意的是Hive計算的結果沒有時分秒,若是須要,依然可使用拼接的方式得到,此處略。

2.日期差


這一小節仍然是上一小節的逆操做。(怎麼這麼多逆操做,累不累啊......)咱們來看一下如何計算兩個時間的日期差。

在pandas中,若是事件類型是datetime64[ns]類型,直接做差就能夠得出日期差,可是獲得的數據後面還有一個"days"的單位,這其實就是上一小節提到的timedelta類型。

爲了便於使用,咱們使用map函數獲取其days屬性,獲得咱們想要的數值的差。以下所示:

若是不是datetime格式,能夠先用下面的代碼進行一次轉換。

#str_ts是字符串格式,轉換出的dt_ts是datetime64[ns]格式
data['dt_ts'] = pd.to_datetime(data['str_ts'], format='%Y-%m-%d %H:%M:%S')


Hive和MySQL中的日期差有相應的函數datediff。但須要注意它的輸入格式。



能夠看到輸入的形式既能夠是具體到時分秒的格式,也能夠是年月日格式。可是要注意Hive中輸入的日期必須是10位的格式,不然得不到正確的結果,好比輸入8位的,結果會是NULL,而MySQL則能夠進行8位日期的計算。



◆ ◆ ◆  ◆ 

小結



本文涉及到的對比操做和相應的解法如上圖所示。總體看起來比以前的要「亂」一些,但仔細看看並無多少內容。


須要指出,關於日期操做,本文只是總結了一些pandas和SQL都有的部分操做,也都是比較常見的。python中和SQL自己關於日期操做還有不少其餘用法,限於時間關係就省略了。


因爲時間匆忙,行文不當之處還請多多包含。若是你有好的想法,歡迎一塊兒交流學習。本文的代碼和數據能夠在公衆號後臺回覆「對比三」獲取,祝學習愉快!


以清淨心看世界;

用歡喜心過生活。

超哥的雜貨鋪,你值得擁有~

添加微信hitchenghengchao進入交流羣~

長按二維碼關注咱們


推薦閱讀:

1.一場pandas與SQL的巔峯大戰

2.一場pandas與SQL的巔峯大戰(二)

3.經常使用Hive函數的學習和總結

本文分享自微信公衆號 - 超哥的雜貨鋪(gh_a624b94bfdab)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索