你们好,我又来了,在通过以前两篇文章的介绍后相信你们对itertools的一些常见的好用的方法有了一个大体的了解,我本身在学完以后仿照别人的例子进行了真实场景下的模拟练习,今天和你们一块儿分享,有不少部分还能够优化,但愿有更好主意和建议的朋友们能够留言哈,让咱们一块儿进步python
在这个例子中,咱们首先尝试使用itertools来操做大型数据集:标准普尔500指数的历史每日价格数据。 我会在这个部分的最后附上下载连接和py文件,这里的数据源来自雅虎财经git
目标: 找到标准普尔500指数的单日最大收益,最大损失(百分比),和最长的增加周期github
首先咱们手上获得了 SP500.csv ,让咱们对数据有个大概的印象,前十行的数据以下:bash
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
复制代码
为了实现目标,具体思路以下:less
这里有关百分比的计算公式以下:函数
首先在这里,咱们会常常处理日期,为了方便后续操做,这里咱们引入collections模块的namedtuple来实现对日期的相关操做:post
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笔记,会为你们都讲到,这里面涉及的小知识点以下:学习
下面为了唤醒你们的记忆,我这里快速举一个有关于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学习笔记中和你们慢慢说的 。好的,如今回到刚才的实战:ui
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就好,这里只是为了让你们有个印象,若是回忆不起来的老铁们也没有关系,轻轻点击如下连接马上重温:
好了,书规正传,咱们发现用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深度解析至此全剧终。