摘要:將每一行作為返回,其中是每行中的列名。對于每一行,都會生成一個對象,其中包含和列中的值。它返回一個迭代器,是迭代結果都為的情況。深度解析至此全劇終。
簡單實戰
大家好,我又來了,在經過之前兩篇文章的介紹后相信大家對itertools的一些常見的好用的方法有了一個大致的了解,我自己在學完之后仿照別人的例子進行了真實場景下的模擬練習,今天和大家一起分享,有很多部分還可以優化,希望有更好主意和建議的朋友們可以留言哈,讓我們一起進步
實戰:分析標準普爾500指數 數據源及目標在這個例子中,我們首先嘗試使用itertools來操作大型數據集:標準普爾500指數的歷史每日價格數據。 我會在這個部分的最后附上下載鏈接和py文件,這里的數據源來自雅虎財經
目標: 找到標準普爾500指數的單日最大收益,最大損失(百分比),和最長的增長周期
首先我們手上得到了 SP500.csv ,讓我們對數據有個大概的印象,前十行的數據如下:
Date,Open,High,Low,Close,Adj Close,Volume 1950-01-03,16.660000,16.660000,16.660000,16.660000,16.660000,1260000 1950-01-04,16.850000,16.850000,16.850000,16.850000,16.850000,1890000 1950-01-05,16.930000,16.930000,16.930000,16.930000,16.930000,2550000 1950-01-06,16.980000,16.980000,16.980000,16.980000,16.980000,2010000 1950-01-09,17.080000,17.080000,17.080000,17.080000,17.080000,2520000 1950-01-10,17.030001,17.030001,17.030001,17.030001,17.030001,2160000 1950-01-11,17.090000,17.090000,17.090000,17.090000,17.090000,2630000 1950-01-12,16.760000,16.760000,16.760000,16.760000,16.760000,2970000 1950-01-13,16.670000,16.670000,16.670000,16.670000,16.670000,3330000
為了實現目標,具體思路如下:
讀取csv文件,并利用 Adj Close這一列轉換為每日百分比變化的序列,代表收益,命名為gain
找到gain這一序列中的最大值和最小值,并且找到對應的日期,當然,有可能會出現對應多個日期的情況,我們這里選取日期最近的就好。
定義一個sequence叫做growth_streaks,其中包含了所有 gain中出現的連續為正值的元素組成的tuple,我們要找到這些tuples中長度最長的一個,從而定位其對應的開始時間和結束時間,當然這里也是一樣,有可能出現最大長度一樣的的情況,這種情況下,我們還是選擇日期最近的。
這里有關百分比的計算公式如下:
分步實現首先在這里,我們會經常處理日期,為了方便后續操作,這里我們引入collections模塊的namedtuple來實現對日期的相關操作:
from collections import namedtuple class DataPoint(namedtuple("DataPoint", ["date", "value"])): __slots__ = () def __le__(self, other): return self.value <= other.value def __lt__(self, other): return self.value < other.value def __gt__(self, other): return self.value > other.value
這里有很多小技巧,之后我會再系統的開一個Python OOP筆記,會為大家都講到,這里面涉及的小知識點如下:
slots :這是一個節省變量內存的好東西,__slot__后面一般都是跟class中 init 方法里面用到的變量,好處在于能夠大量節省內存
namedtuple:可以實現類似屬性一樣調用tuple里面的元素,我在collections里面詳細說過,大家可以看看:Python 進階之路 (七) 隱藏的神奇寶藏:探秘Collections
le:運算符重載,可以得到class中一個變量的長度,必須是整數,也就是說如果傳入的是list,dict,tuple,set這些一定沒有問題,因為這些序列的長度一定是整數,這里面傳遞的是tuple()
lt:運算符重載(less than ):可以實現利用 < 比較一個class的不同對象中的值大小的比較
gt:運算符重載(greater than):可以實現利用 > 比較一個class的不同對象中的值大小的比較
下面為了喚醒大家的記憶,我這里快速舉一個有關于namedtuple,le,lt,gt的小栗子:
from collections import namedtuple class Person(namedtuple("person", ["name", "age","city","job"])): def __le__(self): return len(self) def __lt__(self,other): return self.age < other.age def __gt__(self,other): return self.age > other.age xiaobai = Person("xiaobai", 18, "paris","student") laobai = Person("Walter White",52, "albuquerque","cook") print("Infomation for first person: ", xiaobai) # 顯示全部信息 print("Age of second person is: ", laobai.age) # 根據name得到tuple的數據 print(len(xiaobai)) print(xiaobai > laobai) print(xiaobai < laobai) Out: Infomation for first person: Person(name="xiaobai", age=18, city="paris",job="student") Age of second person is: 52 4 False True
如果大家對這個例子中的一些地方還有疑問,不用擔心,我會在下一個專欄Python OOP學習筆記中和大家慢慢說的
。好的,現在回到剛才的實戰:
from collections import namedtuple class DataPoint(namedtuple("DataPoint", ["date", "value"])): __slots__ = () def __le__(self, other): return self.value <= other.value def __lt__(self, other): return self.value < other.value def __gt__(self, other): return self.value > other.value
這里我們的DataPoint類有兩個主要屬性,一個是datetime類型的日期,一個是當天的標普500值
接下來讓我們讀取csv文件,并將每行中的Date和Adj Close列中的值存為DataPoint的對象,最后把所有的對象組合為一個sequence序列:
import csv from datetime import datetime def read_prices(csvfile, _strptime=datetime.strptime): with open(csvfile) as infile: reader = csv.DictReader(infile) for row in reader: yield DataPoint(date=_strptime(row["Date"], "%Y-%m-%d").date(), value=float(row["Adj Close"])) prices = tuple(read_prices("SP500.csv"))
read_prices()生成器打開 SP500.csv 并使用 csv.DictReader()讀取數據的每一行。DictReader()將每一行作為 OrderedDict 返回,其中key是每行中的列名。
對于每一行,read_prices()都會生成一個DataPoint對象,其中包含“Date”和“Adj Close”列中的值。 最后,完整的數據點序列作為元組提交給內存并存儲在prices變量中
Ps: Ordereddict是我在collections中漏掉的知識點,我馬上會補上,大家可以隨時收藏Python 進階之路 (七) 隱藏的神奇寶藏:探秘Collections,我會繼續更新
接下來我們要把prices這個轉變為表達每日價格變化百分比的序列,利用的公式就是剛才提到的,如果忘了的朋友可以往回翻~
gains = tuple(DataPoint(day.date, 100*(day.value/prev_day.value - 1.)) for day, prev_day in zip(prices[1:], prices))
為了得到標普500單日最大漲幅,我們可以用一下方法:
max_gain = DataPoint(None, 0) for data_point in gains: max_gain = max(data_point, max_gain) print(max_gain) # DataPoint(date="2008-10-28", value=11.58)
我們可以把這個方法用之前提到過的reduce簡化一下:
import functools as ft max_gain = ft.reduce(max, gains) print(max_gain) # DataPoint(date="2008-10-28", value=11.58)
這里有關reduce 和 lambda的用法,我們可以通過一個小栗子來回憶一下:
import functools as ft x = ft.reduce(lambda x,y:x+y,[1, 2, 3, 4, 5]) print(x) Out: 15
當然,如果求和在實際場景直接用sum就好,這里只是為了讓大家有個印象,如果回憶不起來的老鐵們也沒有關系,輕輕點擊以下鏈接立刻重溫:
Python 進階之路 (五) map, filter, reduce, zip 一網打盡
Python 進階之路 (六) 九淺一深 lambda,陳獨秀你給我坐下!
好了,書規正傳,我們發現用reduce改進了for循環后得到了同樣的結果,單日最大漲幅的日期也一樣,但是這里需要注意的是reduce和剛才的for循環完全不是一回事
我們可以想象一下,假如CSV文件中的數據每天都是跌的話。 max_gain最后到底是多少?
在 for 循環中,首先設置max_gain = DataPoint(None,0),因此如果沒有漲幅,則最終的max_gain值將是此空 DataPoint 對象。但是,reduce()解決方案會返回最小的單日跌幅,這不是我們想要的,可能會引入一個難以找到的bug
這就是itertools可以幫助到我們的地方。 itertools.filterfalse()函數有兩個參數:一個返回True或False的函數,和一個可迭代的輸入。它返回一個迭代器,是迭代結果都為False的情況。這里是個小栗子:
import itertools as it only_positives = it.filterfalse(lambda x: x <= 0, [0, 1, -1, 2, -2]) print(list(only_positives)) Out:[1, 2]
所以現在我們可以用 itertools.filterfalse()去除掉gains中那些小于0或者為負數的值,這樣reduce會僅僅作用在我們想要的正收益上:
max_gain = ft.reduce(max, it.filterfalse(lambda p: p <= 0, gains))
這里我們默認為gains中一定存在大于0的值,這也是事實,但是如果假設gains中沒有的話,我們會報錯,因此在使用itertools.filterfalse()的實際場景中要注意到這一點。
針對這種情況,可能你想到的應對方案是在合適的情況下添加TryExpect捕獲錯誤,但是reduce有個更好的解決方案,reuce里面可以傳遞第三個參數,用做reduce返回結果不存在時的默認值,這一點和字典的get方法有異曲同工之妙,如果對get有疑問的朋友可以回顧我之前的文章:Python 進階之路 (二) Dict 進階寶典,初二快樂!,還是看一個小栗子:
>>> ft.reduce(max, it.filterfalse(lambda x: x <= 0, [-1, -2, -3]), 0) 0
這回很好理解了,因此我們應用到我們標準普爾指數的實戰上:
zdp = DataPoint(None, 0) # zero DataPoint max_gain = ft.reduce(max, it.filterfalse(lambda p: p.value <= 0, diffs), zdp)
同理,對于標普500單日最大跌幅我們也照貓畫虎:
max_loss = ft.reduce(min, it.filterfalse(lambda p: p.value > 0, gains), zdp) print(max_loss) # DataPoint(date="2018-02-08", value=-20.47)
根據我們的數據源是2018年2月8號那一天,我沒有谷歌查詢那一天發生了什么,大家感興趣可以看看哈,但是應該是沒有問題的,因為數據源來自雅虎財經
現在我們已經得到了標普500歷史上的單日最大漲跌的日期,我們接下來要找到它的最長時間段,其實這個問題等同于在gains序列中找到最長的連續為正數的點的集合,itertools.takewhile()和itertools.dropwhile()函數非常適合處理這種情況。
itertools.takewhile()接受兩個參數,一個為判斷的條件,一個為可迭代的序列,會返回第一個判斷結果為False時之前的迭代過的所有元素,下面的小栗子很好的解釋了這一點
it.takewhile(lambda x: x < 3, [0, 1, 2, 3, 4]) # 0, 1, 2
itertools.dropwhile() 則恰恰相反:
it.dropwhile(lambda x: x < 3, [0, 1, 2, 3, 4]) # 3, 4
因此我們可以創建一下方法來實現在gains中找到連續為正數的序列:
def consecutive_positives(sequence, zero=0): def _consecutives(): for itr in it.repeat(iter(sequence)): yield tuple(it.takewhile(lambda p: p > zero, it.dropwhile(lambda p: p <= zero, itr))) return it.takewhile(lambda t: len(t), _consecutives()) growth_streaks = consecutive_positives(gains, zero=DataPoint(None, 0)) longest_streak = ft.reduce(lambda x, y: x if len(x) > len(y) else y, growth_streaks)
最后讓我們看一下完整的代碼:
from collections import namedtuple import csv from datetime import datetime import itertools as it import functools as ft class DataPoint(namedtuple("DataPoint", ["date", "value"])): __slots__ = () def __le__(self, other): return self.value <= other.value def __lt__(self, other): return self.value < other.value def __gt__(self, other): return self.value > other.value def consecutive_positives(sequence, zero=0): def _consecutives(): for itr in it.repeat(iter(sequence)): yield tuple(it.takewhile(lambda p: p > zero, it.dropwhile(lambda p: p <= zero, itr))) return it.takewhile(lambda t: len(t), _consecutives()) def read_prices(csvfile, _strptime=datetime.strptime): with open(csvfile) as infile: reader = csv.DictReader(infile) for row in reader: yield DataPoint(date=_strptime(row["Date"], "%Y-%m-%d").date(), value=float(row["Adj Close"])) # Read prices and calculate daily percent change. prices = tuple(read_prices("SP500.csv")) gains = tuple(DataPoint(day.date, 100*(day.value/prev_day.value - 1.)) for day, prev_day in zip(prices[1:], prices)) # Find maximum daily gain/loss. zdp = DataPoint(None, 0) # zero DataPoint max_gain = ft.reduce(max, it.filterfalse(lambda p: p.value <= zdp, gains)) max_loss = ft.reduce(min, it.filterfalse(lambda p: p.value > zdp, gains), zdp) # Find longest growth streak. growth_streaks = consecutive_positives(gains, zero=DataPoint(None, 0)) longest_streak = ft.reduce(lambda x, y: x if len(x) > len(y) else y, growth_streaks) # Display results. print("Max gain: {1:.2f}% on {0}".format(*max_gain)) print("Max loss: {1:.2f}% on {0}".format(*max_loss)) print("Longest growth streak: {num_days} days ({first} to {last})".format( num_days=len(longest_streak), first=longest_streak[0].date, last=longest_streak[-1].date ))
最終結果如下:
Max gain: 11.58% on 2008-10-13 Max loss: -20.47% on 1987-10-19 Longest growth streak: 14 days (1971-03-26 to 1971-04-15)
數據源可以點擊這里下載
總結這次我為大家梳理一個利用itertools進行了簡單實戰的小栗子,這里我們旨在多深入了解itertools,但是真實的生活中,遇到這種問題,哪有這么麻煩,一個pandas包就搞定了,我以后會和大家分享和pandas有關的知識,這一次接連三期的itertools總結希望大家喜歡。
itertools深度解析至此全劇終。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/43247.html
前情回顧 大家好,我又回來了。今天我會繼續和大家分享itertools這個神奇的自帶庫,首先,讓我們回顧一下上一期結尾的時候我們講到的3個方法: combinations() combinations_with_replacement() permutations() 讓我們對這3個在排列組合中經常會使用到的函數做個總結 combinations() 基礎概念 模板:combinations...
摘要:例如,以下對兩個的相應元素求和這個例子很好的解釋了如何構建中所謂的迭代器代數的函數的含義。為簡單起見,假設輸入的長度可被整除。接受兩個參數一個可迭代的正整數最終會在中個元素的所有組合的元組上產生一個迭代器。 前言 大家好,今天想和大家分享一下我的itertools學習體驗及心得,itertools是一個Python的自帶庫,內含多種非常實用的方法,我簡單學習了一下,發現可以大大提升工作...
摘要:與上面的操作類似,可以使用多種運算符和方法來更改集合的內容。通過修改集合元素方法運算符用法通過修改集合和作用是向集合中添加中所有不存在的元素。 Set是什么 大家好,恰逢初五迎財神,先預祝大家新年財源滾滾?。≡谏弦黄谠斀鈚uple元組的用法后,今天我們來看Python里面最后一種常見的數據類型:集合(Set) 與dict類似,set也是一組key的集合,但不存儲value。由于key不...
摘要:什么是推導式大家好,今天為大家帶來問我最喜歡的推導式使用指南,讓我們先來看看定義推導式是的一種獨有特性,推導式是可以從一個數據序列構建另一個新的數據序列的結構體。 什么是推導式 大家好,今天為大家帶來問我最喜歡的Python推導式使用指南,讓我們先來看看定義~ 推導式(comprehensions)是Python的一種獨有特性,推導式是可以從一個數據序列構建另一個新的數據序列的結構體。...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
閱讀 1426·2021-11-22 09:34
閱讀 1389·2021-09-22 14:57
閱讀 3422·2021-09-10 10:50
閱讀 1410·2019-08-30 15:54
閱讀 3703·2019-08-29 17:02
閱讀 3485·2019-08-29 12:54
閱讀 2627·2019-08-27 10:57
閱讀 3328·2019-08-26 12:24