本文目錄以下:
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 *, localtimestamp, localtimestamp()
FROM `t_order`;
#HiveQL
select *, substr(current_timestamp, 1, 19), substr(current_timestamp(), 1, 19)
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, 1, 10), substr(ts, 1, 4), substr(ts, 6, 2),
substr(ts, 9, 2), substr(ts, 12, 8), substr(ts, 12, 2),
substr(ts, 15, 2), substr(ts, 18, 2)
from t_order;
#HiveQL
select ts, substr(ts, 1, 10), substr(ts, 1, 4), substr(ts, 6, 2),
substr(ts, 9, 2), substr(ts, 12, 8), substr(ts, 12, 2),
substr(ts, 15, 2), substr(ts, 18, 2)
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, 1, 10), '-', '')
from t_order;
#Hive
select *, regexp_replace(substr(ts, 1, 10),'-','')
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, 1, 4), '-', substr(dt8, 5, 2), '-', substr(dt8, 7,2))
from
(
select *, replace(substr(ts, 1, 10), '-', '') as dt8
from t_order
) a
#Hive
select id, ts, concat(substr(dt8, 1, 4), '-', substr(dt8, 5, 2), '-', substr(dt8, 7,2))
from
(
select *, regexp_replace(substr(ts, 1, 10),'-','') 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, 1, 10),'-', ''),
from_unixtime(unix_timestamp(cast(replace(substr(ts, 1, 10),'-', '')as int)),'%Y-%m-%d')
from t_order
;
#Hive
select *,
regexp_replace(substr(ts, 1, 10),'-', ''),
from_unixtime(unix_timestamp(regexp_replace(substr(ts, 1, 10),'-', ''), '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進入交流羣~
長按二維碼關注咱們
推薦閱讀:
本文分享自微信公衆號 - 超哥的雜貨鋪(gh_a624b94bfdab)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。