換個角度,站在買方思維上看問題。
2019 年 5 月 $Beyond Meat(BYND)$ 美國納斯達克掛牌上市,募集資金 2.41 億美元,成為人造肉行業第一股,也是 2000 年以來市值 2億以上公司中上市首日漲幅最高的,股價收漲163%,引起了極高關注。此外由于其吸引了比爾蓋茨、萊昂納多迪卡普里及麥當勞前任 CEO Don Thompson 等眾多明星投資人,更是將「人造肉」這個行業帶來了超高關注度,從最初的發行價 45 美元一路上漲到最高的 239 美元左右,然后又一路下跌,截止到 2019 年 12月 31 日,股價徘徊在 75 美元左右,當前市值大概 46 億美金。
上漲 5 倍后狂跌 70%,2020年的「人造肉」明星BYND還值得投資嗎?
伴隨著 BYND 的上市的這半年來,國內投資者對 BYND 的投資前景的最大疑問就是:真的會有那么多人選擇吃假肉嗎?難道真肉它不香嗎?
香!毫無疑問!
首先為什么那么多人質疑人造肉,最根本的一點是人造肉無法在口感和賣相上完全達到真肉水平。現有的人造肉主要以碎肉形態生 產出來,盡管可以通過調味上色等方式模擬真肉的外表形態,但是并不能模仿咀 嚼骨頭、筋膜等特殊部位的口感。但是2019年1月Impossible food 從大豆植物根部提取豆血紅蛋白分子,并添加入產品當中。使得素肉和絞碎的牛肉一樣有嚼勁,與真肉更加相似。
但是這些估計也是沒辦法吸引肉食愛好者們選擇去吃「人造肉」的。
到底是哪些人在吃人造肉呢?
當前情況下,主力消費群體是那些追求健康和環保的人,有責任意識的飲食習慣,成為高收入人群和新興中產階級追求的消費趨勢。
當前人造肉的主要市場在美國、歐洲、中國香港、新加坡等國家地區,其中美國在全球植物性人造肉市場中占近 40% 的市場份額。
與西方素食文化長期浸潤在社會運動中不同,國內植物肉行業更單純,人造肉更多是為了豐富中國人餐桌、滿足一些宗教人士需求,并沒有直接和主食肉類形成直接競爭。國內已經有一些素食廠家小有名氣,比如蘇州鴻昶素食、寧波素蓮食品、深圳齊善素食,從 2018 年國內三家公司的營業額來看,深圳齊善 3 億元,為國內三大企業中最高。江蘇鴻昶和寧波素蓮分別為 6000 萬元和 3000 萬元。但與 Beyond Meat 在 2018 年實現了 8790 萬美元的營收相比,國內三家植物肉企業整體規模不抵一個 Beyond Meat。
根據 Winsight Grocery 和 Acosta 對美國千禧一代選擇無肉飲食原因的調查研究,其中 51%的受訪者是出于身體健康的原因選擇素食主義,還有 32%及 30%的受訪者因為環境保護和保護動物的意識選擇吃素食。
根據 Beyond Meat 官網,長期食用普通肉類會增加 16%的癌癥以及 21%的心臟病的患病風險;此外,人造肉比傳統肉類更綠色、更安全,從根本上防止了很多人畜共患病和動物疫病的傳播,如瘋牛病、口蹄疫、非洲豬瘟等;最后在營養價值上,植物性人造肉不僅具有低膽固醇、較低水平的飽和脂肪和少量膳食纖維等優點,而且在未來預計可以根據人類所需生產含有特定營養物質的肉類。
聯合糧農組織(FAO)早在 2003年就提出了畜牧業減排行動,因為牲畜是溫室氣體排放的主要來源。此外,畜牧業產生的肉類會消耗大量土地、糧食作物、水資源等自然資源。如果能生產人造肉來代替部分畜牧養殖的肉類,就可以大大減少溫室氣體排放,并且降低飼料、土地和水資源等資源的需求。
據 Harris Interactive 的調查顯示,從 2008 年到 2015 年,美國素食主義者的人數從 700 多萬增長到 1000 多萬,占比從 3.2%增長到 3.4%。Veganlife 雜志稱,過去十年中,英國的素食群體從 15 萬增加到了 50 萬,足足翻了三倍。有責任意識的飲食習慣在發達國家更加受歡迎,部分發展中國家的中高產消費者也支持這種消費趨勢。
根據 OECD 的數據,2018 年中國肉類消費量達到 8829.6 萬噸,歐盟與美國肉類消費量分別為 4426.7 萬噸和 4134.9 萬噸。目前美國肉類人均消費量為 100 千克左右,而中國僅為美國的一半左右。因此,未來中國的肉類消費量將進一步增加,預計 2030 年中國肉類產品的供給缺口將達到 3800 萬噸以上,該不足部分可由有人造肉來填補。
當前行業主要問題是成本較高,其中以植物蛋白為技術路線的“素肉”產品價格稍高于傳統肉制品,尚可被市場接受。
隨著規模化生產的擴大、餐飲及商超等零售渠道的推廣等原因,生產成本還會進 一步下降。
根據 Beyond Meat 預測,類比植物奶行業在美國取得的成功,全美人造肉市場未來將達到 350 億美元。因為根據市場研究公司Mintel 的測算,2017 年美國非乳制奶類別的市場約為 20 億元,相當于乳制奶的 13%,并且在 2012 年至 2017 年連續保持 10%的高增長率。如果“植物肉”市場規模可以達到類似“植物奶”與乳制奶的比例,那么對應美國 2700 億肉類市場,這意味著 350 億美元的新增市場空間。如果以全球肉類超 1.4 萬億美元的市場進行模糊測算,則可達1820 億美元。
▍Beyond Meat 主要產品:超越漢堡、超越香腸、超越牛肉碎、超越牛肉:
▍Beyond Meat 季度營收:
▍Beyond Meat 季度凈利潤:
▍Beyond Meat 季度毛利率 & 凈利率:
第三季度,BYND銷售額為9200萬美元,每股收益為6美分,這是該公司上市后第一個實現盈利的季度。銷售額比華爾街預期高出1000萬美元。此外,Beyond Meat還在過去兩個季度第二次提高了全年銷售預期。最初預期為2.1億美元,7月份提高至2.4億美元,現在的預期是2.7億美元。市場對該公司產品的需求仍然旺盛。
Beyond Meat 無論在零售還是在餐飲渠道,都保持著良好的增長趨勢。
▍Beyond Meat 覆蓋餐廳以及商店:
公司近年研發開支占總營業開支的比重始終保持在 20%以上,2018 年研發開支為 960 萬美元,同比增長 67.5%,上市募集資金中計劃 5000-6000 萬美元用于研發以及銷售開支,按照 20%比例計算預估將新增 1000-1200 萬美元的研發投入。截至 2019 年 3 月 30 日,公司雇傭了大約 63 名科學家、工程師、研究人員、技術人員和廚師,進行新產品的研發,公司的目標是每年至少開發一種新產品。同時,公司不斷推進現有產品的改進,如更好模擬脂肪組織和飽和脂肪、尋找替代功能蛋白質、額外的結締組織等效物、 封裝材料和技術以及更好的風味和質地開發等。
▍主要競爭者:Impossible foods
未上市。在我看來,當前「人造肉」市場遠未飽和,任何一家競爭對手的脫穎而出都會引爆市場。
上市后,BYND的波動性很大,一直是市場上空頭頭寸最多的股票之一,這意味著看跌的投資者正積極押注該股會走低。他們認為該公司的估值升得太高太快,遠期市盈率達到15倍左右。
但是對于投資者來說,最好還是把關注焦點放在他們是否認為未來10年人造肉將增長成為一個數十億美元的全球性市場。
從我的觀點來看,2020年投資 Beyond Meat 是值得期待的。
從最近的三季度報表看,Beyond Meat 現在的業務模式是完全有利可圖的,兩度高調調高全年預期,意味著 Beyond Meat 的盈利和高速增長不是一兩個季度僥幸的結果,那些對長期虧損對初創企業持懷疑態度的人,應該更關注企業內部的盈利細節。
特斯拉、瑞幸咖啡、拼多多,最近這些漲幅巨大的股票,都是在上漲之處擁有巨大的反對聲音,很多人都恨不得全倉做空看著他破產退市,然而呢?
市場上,不要總是站在賣方思維看問題,那些思考市盈率等指標的賣方思維者,即使看到指標反轉了,也不會入手的。
文末附源代碼等資源文件
別忘了收藏
數據對象
pandas主要有兩種數據對象:Series、DataFrame
注: 后面代碼使用pandas版本0.20.1,通過import pandas as pd引入
Series是一種帶有索引的序列對象。
簡單創建如下:
# 通過傳入一個序列給pd.Series初始化一個Series對象, 比如list
s1=pd.Series(list("1234"))
print(s1)
0 1
1 2
2 3
3 4
dtype:object
類似與數據庫table有行列的數據對象。
# 通過傳入一個numpy的二維數組或者dict對象給pd.DataFrame初始化一個DataFrame對象
# 通過numpy二維數組
import numpy as np
df1=pd.DataFrame(np.random.randn(6,4))
print(df1)
0 1 2 3
0 -0.646340 -1.249943 0.393323 -1.561873
1 0.371630 0.069426 1.693097 0.907419
2 -0.328575 -0.256765 0.693798 -0.787343
3 1.875764 -0.416275 -1.028718 0.158259
4 1.644791 -1.321506 -0.33742
5 0.8206895 0.006391 -1.447894 0.506203 0.977295
# 通過dict字典
df2=pd.DataFrame({ 'A' : 1.,
'B' : pd.Timestamp('20130102'),
'C' :pd.Series(1,index=list(range(4)),dtype='float32'),
'D' : np.array([3] * 4,dtype='int32'),
'E' : pd.Categorical(["test","train","test","train"]),
'F' : 'foo' })
print(df2)
A B C D E F
0 1.0 2013-01-02 1.0 3 test foo
1 1.0 2013-01-02 1.0 3 train foo
2 1.0 2013-01-02 1.0 3 test foo
3 1.0 2013-01-02 1.0 3 train foo
不管是Series對象還是DataFrame對象都有一個對對象相對應的索引,Series的索引類似于每個元素, DataFrame的索引對應著每一行。
查看:在創建對象的時候,每個對象都會初始化一個起始值為0,自增的索引列表, DataFrame同理。
# 打印對象的時候,第一列就是索引
print(s1)
0 1
1 2
2 3
3 4
dtype: object
# 或者只查看索引, DataFrame同理
print(s1.index)
增刪查改
這里的增刪查改主要基于DataFrame對象,為了有足夠數據用于展示,這里選擇tushare的數據。
1. tushare安裝
ipinstall tushare
創建數據對象如下:
import tushare as ts
df=ts.get_k_data("000001")
DataFrame 行列,axis 圖解:
查看每列的數據類型
# 查看df數據類型
df.dtypes
date object
open float64
close float64
high float64
low float64
volume float64
code object
dtype: object
查看指定指定數量的行:head函數默認查看前5行,tail函數默認查看后5行,可以傳遞指定的數值用于查看指定行數。
查看前5行
df.head
date open close high low volume code
0 2015-12-23 9.927 9.935 10.174 9.871 1039018.0 000001
1 2015-12-24 9.919 9.823 9.998 9.744 640229.0 000001
2 2015-12-25 9.855 9.879 9.927 9.815 399845.0 000001
3 2015-12-28 9.895 9.537 9.919 9.537 822408.0 000001
4 2015-12-29 9.545 9.624 9.632 9.529 619802.0 000001
# 查看后5行
df.tail
date open close high low volume code
636 2018-08-01 9.42 9.15 9.50 9.11 814081.0 000001
637 2018-08-02 9.13 8.94 9.15 8.88 931401.0 000001
638 2018-08-03 8.93 8.91 9.10 8.91 476546.0 000001
639 2018-08-06 8.94 8.94 9.11 8.89 554010.0 000001
640 2018-08-07 8.96 9.17 9.17 8.88 690423.0 000001
# 查看前10行
df.head(10)date open close high low volume code
0 2015-12-23 9.927 9.935 10.174 9.871 1039018.0 000001
1 2015-12-24 9.919 9.823 9.998 9.744 640229.0 000001
2 2015-12-25 9.855 9.879 9.927 9.815 399845.0 000001
3 2015-12-28 9.895 9.537 9.919 9.537 822408.0 000001
4 2015-12-29 9.545 9.624 9.632 9.529 619802.0 000001
5 2015-12-30 9.624 9.632 9.640 9.513 532667.0 000001
6 2015-12-31 9.632 9.545 9.656 9.537 491258.0 000001
7 2016-01-04 9.553 8.995 9.577 8.940 563497.0 000001
8 2016-01-05 8.972 9.075 9.210 8.876 663269.0 000001
9 2016-01-06 9.091 9.179 9.202 9.067 515706.0 000001
查看某一行或多行,某一列或多列
# 查看第一行
df[0:1]
date open close high low volume code
0 2015-12-23 9.927 9.935 10.174 9.871 1039018.0 000001
# 查看 10到20行
df[10:21]
date open close high low volume code
10 2016-01-07 9.083 8.709 9.083 8.685 174761.0 000001
11 2016-01-08 8.924 8.852 8.987 8.677 747527.0 000001
12 2016-01-11 8.757 8.566 8.820 8.502 732013.0 000001
13 2016-01-12 8.621 8.605 8.685 8.470 561642.0 000001
14 2016-01-13 8.669 8.526 8.709 8.518 391709.0 000001
15 2016-01-14 8.430 8.574 8.597 8.343 666314.0 000001
16 2016-01-15 8.486 8.327 8.597 8.295 448202.0 000001
17 2016-01-18 8.231 8.287 8.406 8.199 421040.0 000001
18 2016-01-19 8.319 8.526 8.582 8.287 501109.0 000001
19 2016-01-20 8.518 8.390 8.597 8.311 603752.0 000001
20 2016-01-21 8.343 8.215 8.558 8.215 606145.0 000001
# 查看看Date列前5個數據
df["date"].head # 或者df.date.head
0 2015-12-23
1 2015-12-24
2 2015-12-25
3 2015-12-28
4 2015-12-29
Name: date, dtype: object
# 查看看Date列,code列, open列前5個數據
df[["date","code", "open"]].head
date code open
0 2015-12-23 000001 9.927
1 2015-12-24 000001 9.919
2 2015-12-25 000001 9.855
3 2015-12-28 000001 9.895
4 2015-12-29 000001 9.545
使用行列組合條件查詢
# 查看date, code列的第10行
df.loc[10, ["date", "code"]]
date 2016-01-07
code 000001
Name: 10, dtype: object
# 查看date, code列的第10行到20行
df.loc[10:20, ["date", "code"]]
date code
10 2016-01-07 000001
11 2016-01-08 000001
12 2016-01-11 000001
13 2016-01-12 000001
14 2016-01-13 000001
15 2016-01-14 000001
16 2016-01-15 000001
17 2016-01-18 000001
18 2016-01-19 000001
19 2016-01-20 000001
20 2016-01-21 000001
# 查看第一行,open列的數據
df.loc[0, "open"]
9.9269999999999996
通過位置查詢:值得注意的是上面的索引值就是特定的位置。
# 查看第1行
df.iloc[0]
date 2015-12-24
open 9.919
close 9.823
high 9.998
low 9.744
volume 640229
code 000001
Name: 0, dtype: object
# 查看最后一行
df.iloc[-1]
date 2018-08-08
open 9.16
close 9.12
high 9.16
low 9.1
volume 29985
code 000001
Name: 640, dtype: object
# 查看第一列,前5個數值
df.iloc[:,0].head
0 2015-12-24
1 2015-12-25
2 2015-12-28
3 2015-12-29
4 2015-12-30
Name: date, dtype: object
# 查看前2到4行,第1,3列
df.iloc[2:4,[0,2]]
date close
2 2015-12-28 9.537
3 2015-12-29 9.624
通過條件篩選
查看open列大于10的前5行
df[df.open > 10].head
date open close high low volume code
378 2017-07-14 10.483 10.570 10.609 10.337 1722570.0 000001
379 2017-07-17 10.619 10.483 10.987 10.396 3273123.0 000001
380 2017-07-18 10.425 10.716 10.803 10.299 2349431.0 000001
381 2017-07-19 10.657 10.754 10.851 10.551 1933075.0 000001
382 2017-07-20 10.745 10.638 10.880 10.580 1537338.0 000001
# 查看open列大于10且open列小于10.6的前五行
df[(df.open > 10) & (df.open < 10.6)].head
date open close high low volume code
378 2017-07-14 10.483 10.570 10.609 10.337 1722570.0 000001
380 2017-07-18 10.425 10.716 10.803 10.299 2349431.0 000001
387 2017-07-27 10.550 10.422 10.599 10.363 1194490.0 000001
388 2017-07-28 10.441 10.569 10.638 10.412 819195.0 000001
390 2017-08-01 10.471 10.865 10.904 10.432 2035709.0 000001
# 查看open列大于10或open列小于10.6的前五行
df[(df.open > 10) | (df.open < 10.6)].head
date open close high low volume code
0 2015-12-24 9.919 9.823 9.998 9.744 640229.0 000001
1 2015-12-25 9.855 9.879 9.927 9.815 399845.0 000001
2 2015-12-28 9.895 9.537 9.919 9.537 822408.0 000001
3 2015-12-29 9.545 9.624 9.632 9.529 619802.0 000001
4 2015-12-30 9.624 9.632 9.640 9.513 532667.0 000001
在前面已經簡單的說明Series, DataFrame的創建,這里說一些常用有用的創建方式。
# 創建2018-08-08到2018-08-15的時間序列,默認時間間隔為Day
s2=pd.date_range("20180808", periods=7)
print(s2)
DatetimeIndex(['2018-08-08', '2018-08-09', '2018-08-10', '2018-08-11',
'2018-08-12', '2018-08-13', '2018-08-14'],
dtype='datetime64[ns]', freq='D')
# 指定2018-08-08 00:00 到2018-08-09 00:00 時間間隔為小時
# freq參數可使用參數, 參考: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
s3=pd.date_range("20180808", "20180809", freq="H")
print(s2)
DatetimeIndex(['2018-08-08 00:00:00', '2018-08-08 01:00:00',
'2018-08-08 02:00:00', '2018-08-08 03:00:00',
'2018-08-08 04:00:00', '2018-08-08 05:00:00',
'2018-08-08 06:00:00', '2018-08-08 07:00:00',
'2018-08-08 08:00:00', '2018-08-08 09:00:00',
'2018-08-08 10:00:00', '2018-08-08 11:00:00',
'2018-08-08 12:00:00', '2018-08-08 13:00:00',
'2018-08-08 14:00:00', '2018-08-08 15:00:00',
'2018-08-08 16:00:00', '2018-08-08 17:00:00',
'2018-08-08 18:00:00', '2018-08-08 19:00:00',
'2018-08-08 20:00:00', '2018-08-08 21:00:00',
'2018-08-08 22:00:00', '2018-08-08 23:00:00',
'2018-08-09 00:00:00'],
dtype='datetime64[ns]', freq='H')
# 通過已有序列創建時間序列
s4=pd.to_datetime(df.date.head)
print(s4)
0 2015-12-24
1 2015-12-25
2 2015-12-28
3 2015-12-29
4 2015-12-30
Name: date, dtype: datetime64[ns]
# 將df 的索引修改為date列的數據,并且將類型轉換為datetime類型
df.index=pd.to_datetime(df.date)
df.head
date open close high low volume code date
2015-12-24 2015-12-24 9.919 9.823 9.998 9.744 640229.0 000001
2015-12-25 2015-12-25 9.855 9.879 9.927 9.815 399845.0 000001
2015-12-28 2015-12-28 9.895 9.537 9.919 9.537 822408.0 000001
2015-12-29 2015-12-29 9.545 9.624 9.632 9.529 619802.0 000001
2015-12-30 2015-12-30 9.624 9.632 9.640 9.513 532667.0 000001
# 修改列的字段
df.columns=["Date", "Open","Close","High","Low","Volume","Code"]
print(df.head)
Date Open Close High Low Volume Code date
2015-12-24 2015-12-24 9.919 9.823 9.998 9.744 640229.0 000001
2015-12-25 2015-12-25 9.855 9.879 9.927 9.815 399845.0 000001
2015-12-28 2015-12-28 9.895 9.537 9.919 9.537 822408.0 000001
2015-12-29 2015-12-29 9.545 9.624 9.632 9.529 619802.0 000001
2015-12-30 2015-12-30 9.624 9.632 9.640 9.513 532667.0 000001
# 將Open列每個數值加1, apply方法并不直接修改源數據,所以需要將新值復制給df
df.Open=df.Open.apply(lambda x: x+1)
df.head
Date Open Close High Low Volume Code date
2015-12-24 2015-12-24 10.919 9.823 9.998 9.744 640229.0 000001
2015-12-25 2015-12-25 10.855 9.879 9.927 9.815 399845.0 000001
2015-12-28 2015-12-28 10.895 9.537 9.919 9.537 822408.0 000001
2015-12-29 2015-12-29 10.545 9.624 9.632 9.529 619802.0 000001
2015-12-30 2015-12-30 10.624 9.632 9.640 9.513 532667.0 000001
# 將Open,Close列都數值上加1,如果多列,apply接收的對象是整個列
df[["Open", "Close"]].head.apply(lambda x: x.apply(lambda x: x+1))
Open Close
date
2015-12-24 11.919 10.823
2015-12-25 11.855 10.879
2015-12-28 11.895 10.537
2015-12-29 11.545 10.624
2015-12-30 11.624 10.632
通過drop方法drop指定的行或者列。
注意: drop方法并不直接修改源數據,如果需要使源dataframe對象被修改,需要傳入inplace=True,通過之前的axis圖解,知道行的值(或者說label)在axis=0,列的值(或者說label)在axis=1。
# 刪除指定列,刪除Open列
df.drop("Open", axis=1).head #或者df.drop(df.columns[1])
Date Close High Low Volume Code date
2015-12-24 2015-12-24 9.823 9.998 9.744 640229.0 000001
2015-12-25 2015-12-25 9.879 9.927 9.815 399845.0 000001
2015-12-28 2015-12-28 9.537 9.919 9.537 822408.0 000001
2015-12-29 2015-12-29 9.624 9.632 9.529 619802.0 000001
2015-12-30 2015-12-30 9.632 9.640 9.513 532667.0 000001
# 刪除第1,3列. 即Open,High列
df.drop(df.columns[[1,3]], axis=1).head # 或df.drop(["Open", "High], axis=1).head
Date Close Low Volume Code date
2015-12-24 2015-12-24 9.823 9.744 640229.0 000001
2015-12-25 2015-12-25 9.879 9.815 399845.0 000001
2015-12-28 2015-12-28 9.537 9.537 822408.0 000001
2015-12-29 2015-12-29 9.624 9.529 619802.0 000001
2015-12-30 2015-12-30 9.632 9.513 532667.0 000001
pandas常用參數
當數值很大的時候pandas默認會使用科學計數法
# float數據類型以{:.4f}格式顯示,即顯示完整數據且保留后四位
pd.options.display.float_format='{:.4f}'.format
常用函數
# descibe方法會計算每列數據對象是數值的count, mean, std, min, max, 以及一定比率的值
df.describe
Open Close High Low Volume
count 641.0000 641.0000 641.0000 641.0000 641.0000
mean 10.7862 9.7927 9.8942 9.6863 833968.6162
std 1.5962 1.6021 1.6620 1.5424 607731.6934
min 8.6580 7.6100 7.7770 7.4990 153901.0000
25% 9.7080 8.7180 8.7760 8.6500 418387.0000
50% 10.0770 9.0960 9.1450 8.9990 627656.0000
75% 11.8550 10.8350 10.9920 10.7270 1039297.0000
max 15.9090 14.8600 14.9980 14.4470 4262825.0000
# 單獨統計Open列的平均值
df.Open.mean
10.786248049922001
# 查看居于95%的值, 默認線性擬合
df.Open.quantile(0.95)
14.187
# 查看Open列每個值出現的次數
df.Open.value_counts.head
9.8050 12
9.8630 10
9.8440 10
9.8730 10
9.8830 8
Name: Open, dtype: int64
刪除或者填充缺失值。
# 刪除含有NaN的任意行
df.dropna(how='any')
# 刪除含有NaN的任意列
df.dropna(how='any', axis=1)
# 將NaN的值改為5
df.fillna(value=5)
按行或者列排序, 默認也不修改源數據。
# 按列排序
df.sort_index(axis=1).head
Close Code Date High Low Open Volume
date
2015-12-24 9.8230 000001 2015-12-24 9.9980 9.7440 10.9190 640229.0000
2015-12-25 1.0000 000001 2015-12-25 1.0000 9.8150 10.8550 399845.0000
2015-12-28 1.0000 000001 2015-12-28 1.0000 9.5370 10.8950 822408.0000
2015-12-29 9.6240 000001 2015-12-29 9.6320 9.5290 10.5450 619802.0000
2015-12-30 9.6320 000001 2015-12-30 9.6400 9.5130 10.6240 532667.0000
# 按行排序,不遞增
df.sort_index(ascending=False).head
Date Open Close High Low Volume Code
date
2018-08-08 2018-08-08 10.1600 9.1100 9.1600 9.0900 153901.0000 000001
2018-08-07 2018-08-07 9.9600 9.1700 9.1700 8.8800 690423.0000 000001
2018-08-06 2018-08-06 9.9400 8.9400 9.1100 8.8900 554010.0000 000001
2018-08-03 2018-08-03 9.9300 8.9100 9.1000 8.9100 476546.0000 000001
2018-08-02 2018-08-02 10.1300 8.9400 9.1500 8.8800 931401.0000 000001
安裝某一列的值排序
# 按照Open列的值從小到大排序
df.sort_values(by="Open")
Date Open Close High Low Volume Code
date 2016-03-01 2016-03-01 8.6580 7.7220 7.7770 7.6260 377910.0000 000001
2016-02-15 2016-02-15 8.6900 7.7930 7.8410 7.6820 278499.0000 000001
2016-01-29 2016-01-29 8.7540 7.9610 8.0240 7.7140 544435.0000 000001
2016-03-02 2016-03-02 8.7620 8.0400 8.0640 7.7380 676613.0000 000001
2016-02-26 2016-02-26 8.7770 7.7930 7.8250 7.6900 392154.0000 000001
concat, 按照行方向或者列方向合并。
# 分別取0到2行,2到4行,4到9行組成一個列表,通過concat方法按照axis=0,行方向合并, axis參數不指定,默認為0
split_rows=[df.iloc[0:2,:],df.iloc[2:4,:], df.iloc[4:9]]
pd.concat(split_rows)
Date Open Close High Low Volume Code
date
2015-12-24 2015-12-24 10.9190 9.8230 9.9980 9.7440 640229.0000 000001
2015-12-25 2015-12-25 10.8550 1.0000 1.0000 9.8150 399845.0000 000001
2015-12-28 2015-12-28 10.8950 1.0000 1.0000 9.5370 822408.0000 000001
2015-12-29 2015-12-29 10.5450 9.6240 9.6320 9.5290 619802.0000 000001
2015-12-30 2015-12-30 10.6240 9.6320 9.6400 9.5130 532667.0000 000001
2015-12-31 2015-12-31 10.6320 9.5450 9.6560 9.5370 491258.0000 000001
2016-01-04 2016-01-04 10.5530 8.9950 9.5770 8.9400 563497.0000 000001
2016-01-05 2016-01-05 9.9720 9.0750 9.2100 8.8760 663269.0000 000001
2016-01-06 2016-01-06 10.0910 9.1790 9.2020 9.0670 515706.0000 000001
# 分別取2到3列,3到5列,5列及以后列數組成一個列表,通過concat方法按照axis=1,列方向合并
split_columns=[df.iloc[:,1:2], df.iloc[:,2:4], df.iloc[:,4:]]
pd.concat(split_columns, axis=1).head
Open Close High Low Volume Code date
2015-12-24 10.9190 9.8230 9.9980 9.7440 640229.0000 000001
2015-12-25 10.8550 1.0000 1.0000 9.8150 399845.0000 000001
2015-12-28 10.8950 1.0000 1.0000 9.5370 822408.0000 000001
2015-12-29 10.5450 9.6240 9.6320 9.5290 619802.0000 000001
2015-12-30 10.6240 9.6320 9.6400 9.5130 532667.0000 000001
追加行, 相應的還有insert, 插入插入到指定位置
# 將第一行追加到最后一行
df.append(df.iloc[0,:], ignore_index=True).tail
Date Open Close High Low Volume Code
637 2018-08-03 9.9300 8.9100 9.1000 8.9100 476546.0000 000001
638 2018-08-06 9.9400 8.9400 9.1100 8.8900 554010.0000 000001
639 2018-08-07 9.9600 9.1700 9.1700 8.8800 690423.0000 000001
640 2018-08-08 10.1600 9.1100 9.1600 9.0900 153901.0000 000001
641 2015-12-24 10.9190 9.8230 9.9980 9.7440 640229.0000 000001
由于dataframe是引用對象,所以需要顯示調用copy方法用以復制整個dataframe對象。
繪圖
pandas的繪圖是使用matplotlib,如果想要畫的更細致, 可以使用matplotplib,不過簡單的畫一些圖還是不錯的。
因為上圖太麻煩,這里就不配圖了,可以在資源文件里面查看pandas-blog.ipynb文件或者自己敲一遍代碼。
# 這里使用notbook,為了直接在輸出中顯示,需要以下配置
%matplotlib inline
# 繪制Open,Low,Close.High的線性圖
df[["Open", "Low", "High", "Close"]].plot
# 繪制面積圖
df[["Open", "Low", "High", "Close"]].plot(kind="area")
數據讀寫
讀寫常見文件格式,如csv,excel,json等,甚至是讀取“系統的剪切板”這個功能有時候很有用。直接將鼠標選中復制的內容讀取創建dataframe對象。
# 將df數據保存到當前工作目錄的stock.csv文件
df.to_csv("stock.csv")
# 查看stock.csv文件前5行
with open("stock.csv") as rf:
print(rf.readlines[:5])
['date,Date,Open,Close,High,Low,Volume,Code\n', '2015-12-24,2015-12-24,9.919,9.823,9.998,9.744,640229.0,000001\n', '2015-12-25,2015-12-25,9.855,9.879,9.927,9.815,399845.0,000001\n', '2015-12-28,2015-12-28,9.895,9.537,9.919,9.537,822408.0,000001\n', '2015-12-29,2015-12-29,9.545,9.624,9.632,9.529,619802.0,000001\n']
# 讀取stock.csv文件并將第一行作為index
df2=pd.read_csv("stock.csv", index_col=0)
df2.head
Date Open Close High Low Volume Code
date
2015-12-24 2015-12-24 9.9190 9.8230 9.9980 9.7440 640229.0000 1
2015-12-25 2015-12-25 9.8550 9.8790 9.9270 9.8150 399845.0000 1
2015-12-28 2015-12-28 9.8950 9.5370 9.9190 9.5370 822408.0000 1
2015-12-29 2015-12-29 9.5450 9.6240 9.6320 9.5290 619802.0000 1
2015-12-30 2015-12-30 9.6240 9.6320 9.6400 9.5130 532667.0000 1
# 讀取stock.csv文件并將第一行作為index,并且將000001作為str類型讀取, 不然會被解析成整數
df2=pd.read_csv("stock.csv", index_col=0, dtype={"Code": str})
df2.head
簡單實例
這里以處理web日志為例,也許不太實用,因為ELK處理這些綽綽有余,不過喜歡什么自己來也未嘗不可。
日志文件: https://raw.githubusercontent.com/Apache-Labor/labor/master/labor-04/labor-04-example-access.log
2. 日志格式及示例
# 日志格式
# 字段說明, 參考:https://ru.wikipedia.org/wiki/Access.log
%h%l%u%t \“%r \”%> s%b \“%{Referer} i \”\“%{User-Agent} i \”
# 具體示例
75.249.65.145 US - [2015-09-02 10:42:51.003372] "GET /cms/tina-access-editor-for-download/ HTTP/1.1" 200 7113 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" www.example.com 124.165.3.7 443 redirect-handler - + "-" Vea2i8CoAwcAADevXAgAAAAB TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 701 12118 -% 88871 803 0 0 0 0
解析日志文件
HOST=r'^(?P<host>.*?)'
SPACE=r'\s'
IDENTITY=r'\S+'
USER=r"\S+"
TIME=r'\[(?P<time>.*?)\]'
# REQUEST=r'\"(?P<request>.*?)\"'
REQUEST=r'\"(?P<method>.+?)\s(?P<path>.+?)\s(?P<http_protocol>.*?)\"'
STATUS=r'(?P<status>\d{3})'
SIZE=r'(?P<size>\S+)'
REFER=r"\S+"
USER_AGENT=r'\"(?P<user_agent>.*?)\"'
REGEX=HOST+SPACE+IDENTITY+SPACE+USER+SPACE+TIME+SPACE+REQUEST+SPACE+STATUS+SPACE+SIZE+SPACE+IDENTITY+USER_AGENT+SPACE
line='79.81.243.171 - - [30/Mar/2009:20:58:31 +0200] "GET /exemples.php HTTP/1.1" 200 11481 "http://www.facades.fr/" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Media Center PC 4.0; .NET CLR 2.0.50727)" "-"'
reg=re.compile(REGEX)
reg.match(line).groups
將數據注入DataFrame對象
COLUMNS=["Host", "Time", "Method", "Path", "Protocol", "status", "size", "User_Agent"]
field_lis=
with open("access.log") as rf:
for line in rf:
# 由于一些記錄不能匹配,所以需要捕獲異常, 不能捕獲的數據格式如下
# 80.32.156.105 - - [27/Mar/2009:13:39:51 +0100] "GET HTTP/1.1" 400 - "-" "-" "-"
# 由于重點不在寫正則表達式這里就略過了
try:
fields=reg.match(line).groups
except Exception as e:
#print(e)
#print(line)
pass
field_lis.append(fields)
log_df=pd.DataFrame(field_lis)
# 修改列名
log_df.columns=COLUMNS
def parse_time(value):
try:
return pd.to_datetime(value)
except Exception as e:
print(e)
print(value)
# 將Time列的值修改成pandas可解析的時間格式
log_df.Time=log_df.Time.apply(lambda x: x.replace(":", " ", 1))
log_df.Time=log_df.Time.apply(parse_time)
# 修改index, 將Time列作為index,并drop掉在Time列
log_df.index=pd.to_datetime(log_df.Time)
log_df.drop("Time", inplace=True)
log_df.head
Host Time Method Path Protocol status size User_Agent
Time
2009-03-22 06:00:32 88.191.254.20 2009-03-22 06:00:32 GET / HTTP/1.0 200 8674 "-
2009-03-22 06:06:20 66.249.66.231 2009-03-22 06:06:20 GET /popup.php?choix=-89 HTTP/1.1 200 1870 "Mozilla/5.0 (compatible; Googlebot/2.1; +htt...
2009-03-22 06:11:20 66.249.66.231 2009-03-22 06:11:20 GET /specialiste.php HTTP/1.1 200 10743 "Mozilla/5.0 (compatible; Googlebot/2.1; +htt...
2009-03-22 06:40:06 83.198.250.175 2009-03-22 06:40:06 GET / HTTP/1.1 200 8714 "Mozilla/4.0 (compatible; MSIE 7.0; Windows N...
2009-03-22 06:40:06 83.198.250.175 2009-03-22 06:40:06 GET /style.css HTTP/1.1 200 1692 "Mozilla/4.0 (compatible; MSIE 7.0; Windows N...
查看數據類型
# 查看數據類型
log_df.dtypes
Host object
Time datetime64[ns]
Method object
Path object
Protocol object
status object
size object
User_Agent object
dtype: object
由上可知, 除了Time字段是時間類型,其他都是object,但是Size, Status應該為數字
def parse_number(value):
try:
return pd.to_numeric(value)
except Exception as e:
pass
return 0
# 將Size,Status字段值改為數值類型
log_df[["Status","Size"]]=log_df[["Status","Size"]].apply(lambda x: x.apply(parse_number))
log_df.dtypes
Host object
Time datetime64[ns]
Method object
Path object
Protocol object
Status int64
Size int64
User_Agent object
dtype: object
統計status數據
# 統計不同status值的次數
log_df.Status.value_counts
200 5737
304 1540
404 1186
400 251
302 37
403 3
206 2
Name: Status, dtype: int64
繪制pie圖
log_df.Status.value_counts.plot(kind="pie", figsize=(10,8))
查看日志文件時間跨度
log_df.index.max - log_df.index.min
Timedelta('15 days 11:12:03')
分別查看起始,終止時間
print(log_df.index.max)
print(log_df.index.min)
2009-04-06 17:12:35
2009-03-22 06:00:32
按照此方法還可以統計Method, User_Agent字段 ,不過User_Agent還需要額外清洗以下數據。
統計top 10 IP地址
91.121.31.184 745
88.191.254.20 441
41.224.252.122 420
194.2.62.185 255
86.75.35.144 184
208.89.192.106 170
79.82.3.8 161
90.3.72.207 157
62.147.243.132 150
81.249.221.143 141
Name: Host, dtype: int64
繪制請求走勢圖
log_df2=log_df.copy
# 為每行加一個request字段,值為1
log_df2["Request"]=1
# 每一小時統計一次request數量,并將NaN值替代為0,最后繪制線性圖,尺寸為16x9
log_df2.Request.resample("H").sum.fillna(0).plot(kind="line",figsize=(16,10))
分別繪圖
分別對202,304,404狀態重新取樣,并放在一個列表里面
req_df_lis=[
log_df2[log_df2.Status==200].Request.resample("H").sum.fillna(0),
log_df2[log_df2.Status==304].Request.resample("H").sum.fillna(0),
log_df2[log_df2.Status==404].Request.resample("H").sum.fillna(0)
]
# 將三個dataframe組合起來
req_df=pd.concat(req_df_lis,axis=1)
req_df.columns=["200", "304", "404"]
# 繪圖
req_df.plot(figsize=(16,10))
https://github.com/youerning/blog/tree/master/pandas
End.
作者:youerning
來源:51CTO博客
深圳線下·愛數據社區工具系列課程
工具課程:PowerBI 零基礎入門+實踐
活動時間:1月5日 13:00-17:00
課程內容:入門PowerPivot;Power BI報表實戰:基于愛數據學員在線數據庫建立簡單數據看板
溫馨提示:課程需要現場安裝PowerBI工具,請參加課程的同學記得攜帶筆記本電腦