最近工作中,有一个场景,是从缓存中将数据读取出来,再聚合。
当时想到了三种方案:
- 使用dict的方式累加
- 使用数据库的临时表进行数据聚合
- 使用pandas汇总
方式一、以前使用php写过,考虑过不优雅,就放弃了
方式二、由于数据多,每次处理都要先写入数据库,然后再聚合,有点耗时,这方式也在线上测试了,
时间确实比较久。
所以采用了第三种方案:
我先贴出我的代码:
为了,剔除敏感信息,我做了混淆和简化
def sync_data_to_db():
"""
将缓存中的数据聚合后,持久化到db
"""
today = datetime.datetime.now().strftime('%Y-%m-%d')
resp = data_api.get_data()
data_frame = pd.DataFrame([], columns=['uid', 'age', 'country'])
for line in resp.iter_lines():
user = json.loads(line.decode('utf-8'))
uid = user.get('uid')
age = user.get('age')
countries = user.get('country', [])
if countries:
for i in countries:
df = pd.DataFrame([[uid, age, country, i]], columns=['uid', 'age', 'country'])
data_frame = data_frame.append(df)
else:
df = pd.DataFrame([[uid, age, country, 'Unknow']], columns=['uid', 'age', 'country'])
data_frame = data_frame.append(df)
data_frame = data_frame.groupby(['age', 'country'])\
.agg({'uid': {'user_count': len}})
UserCount.objects.filter(dt=today).delete()
for i in data_frame.iterrows():
age, country = i[0]
cnt = i[1].uid.user_count
user = UserCount.objects.filter(
dt=today,
age=age,
country=country
).first()
if user:
setattr(user, 'cnt', cnt)
else:
user = UserCount(
dt=today,
age=age,
country=country
cnt=cnt
)
user.save()
print(datetime.datetime.now())
下面开始介绍pandas的使用
Pandas介绍
pandas是一个提供快速、可扩展和展现数据结构的Python库。目标是成为成为使用Python处理实践和实际数据分析的模块。并且想成为任何语言都能使用的最强大的可扩展的数据操作与分析开源工具。
主要的特性如下:
- 为浮点数和与浮点数精度丢失提供了简易的处理方法。
- 大数据(数字很大,不是通常意义的大数据)的处理。
- 自动而准确地处理数据队列。
- 功能强大。
- 能方便地转换不规则数据和差异数据。
- 智能地处理大数据集的切片、子集。
- 智能合并和连接数据集。
- 灵活地调整数据集。
- 从CSV、Excel和数据库中导入数据。
1、Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使Python成为强大而高效的数据分析环境的重要因素之一。
2、Pandas 是python的一个数据分析包,最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。panel data是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。
3、数据结构:
Series:一维数组,与Numpy中的一维array类似。二者与Python基本的数据结构List也很相近,其区别是:List中的元素可以是不同的数据类型,而Array和Series中则只允许存储相同的数据类型,这样可以更有效的使用内存,提高运算效率。
Time- Series:以时间为索引的Series。
DataFrame:二维的表格型数据结构。很多功能与R中的data.frame类似。可以将DataFrame理解为Series的容器。以下的内容主要以DataFrame为主。
Panel :三维的数组,可以理解为DataFrame的容器。
Pandas 有两种自己独有的基本数据结构。读者应该注意的是,它固然有着两种数据结构,因为它依然是 Python 的一个库,所以,Python 中有的数据类型在这里依然适用,也同样还可以使用类自己定义数据类型。只不过,Pandas 里面又定义了两种数据类型:Series 和 DataFrame,它们让数据操作更简单了。
三、 Pandas使用
1、导入pandas模块并使用别名,以及导入Series模块,以下使用基于本次导入。
In [1]: from pandas import Series
In [2]: import pandas as pd
2、Series
Series 就如同列表一样,一系列数据,每个数据对应一个索引值。
Series 就是“竖起来”的 list:
In [3]: s = Series([1,2,3,4, "youdi", 'king', "world"])
In [4]: s
Out[4]:
0 1
1 2
2 3
3 4
4 youdi
5 king
6 world
dtype: object
另外一点也很像列表,就是里面的元素的类型,由你任意决定(其实是由需要来决定)。
这里,我们实质上创建了一个 Series 对象,这个对象当然就有其属性和方法了。比如,下面的两个属性依次可以显示 Series 对象的数据值和索引:
In [5]: s.index
Out[5]: RangeIndex(start=0, stop=7, step=1)
In [6]: s.values
Out[6]: array([1, 2, 3, 4, 'youdi', 'king', 'world'], dtype=object)
列表的索引只能是从 0 开始的整数,Series 数据类型在默认情况下,其索引也是如此。不过,区别于列表的是,Series 可以自定义索引:
In [7]: s2 = Series(["youdi", 24, 'man'], index=['name', 'age', 'sex'])
In [8]: s2
Out[8]:
name youdi
age 24
sex man
dtype: object
每个元素都有了索引,就可以根据索引操作元素了。还记得 list 中的操作吗?Series 中,也有类似的操作。先看简单的,根据索引查看其值和修改其值:
In [10]: s2.get('name')
Out[10]: 'youdi'
In [11]: s2.get('age')
Out[11]: 24
In [12]: s2.get('sex')
Out[12]: 'man'
In [13]: s2['name'] = "liangchangyou"
In [14]: s2['name']
Out[14]: 'liangchangyou'
In [15]: s2
Out[15]:
name liangchangyou
age 24
sex man
dtype: object
这是不是又有点类似 dict 数据了呢?的确如此。看下面就理解了。
读者是否注意到,前面定义 Series 对象的时候,用的是列表,即 Series() 方法的参数中,第一个列表就是其数据值,如果需要定义 index,放在后面,依然是一个列表。除了这种方法之外,还可以用下面的方法定义 Series 对象:
In [16]: adobe = {"ps": 200, "ae": 300, 'pr': 400, "ai": 500}
In [17]: s_adobe = Series(adobe)
In [18]: s_adobe
Out[18]:
ae 300
ai 500
pr 400
ps 200
dtype: int64
现在是否理解为什么前面那个类似 dict 了?因为本来就是可以这样定义的。
这时候,索引依然可以自定义。Pandas 的优势在这里体现出来,如果自定义了索引,自定的索引会自动寻找原来的索引,如果一样的,就取原来索引对应的值,这个可以简称为“自动对齐”。
In [19]: s_ph = Series(adobe, index=["ps", "ae", "pr", "ai"])
In [20]: s_ph
Out[20]:
ps 200
ae 300
pr 400
ai 500
dtype: int64
In [21]: s_ph = Series(adobe, index=["ps", "ae", "pr", "ai", "maya"])
In [22]:
In [22]: s_ph
Out[22]:
ps 200.0
ae 300.0
pr 400.0
ai 500.0
maya NaN
dtype: float64
在 Pandas 中,如果没有值,都对齐赋给 NaN。
Pandas 有专门的方法来判断值是否为空。
In [23]: pd.isnull(s_ph)
Out[23]:
ps False
ae False
pr False
ai False
maya True
dtype: bool
此外,Series 对象也有同样的方法:
In [24]: s_ph.isnull()
Out[24]:
ps False
ae False
pr False
ai False
maya True
dtype: bool
其实,对索引的名字,是可以从新定义的:
In [25]: s_ph.index = ['python', 'php', 'c', 'golang', 'sql']
In [26]: s_ph
Out[26]:
python 200.0
php 300.0
c 400.0
golang 500.0
sql NaN
dtype: float64
对于 Series 数据,也可以做类似下面的运算(关于运算,后面还要详细介绍):
In [27]: s_ph * 3
Out[27]:
python 600.0
php 900.0
c 1200.0
golang 1500.0
sql NaN
dtype: float64
Series就先简要写到这,下面看pandas的另一种数据结构DataFrame.
DataFrame
DataFrame 是一种二维的数据结构,非常接近于电子表格或者类似 mysql 数据库的形式。它的竖行称之为 columns,横行跟前面的 Series 一样,称之为 index,也就是说可以通过 columns 和 index 来确定一个主句的位置。
首先来导入模块
In [28]: from pandas import Series,DataFrame
In [30]: names = ['youdi', 'rino', 'jackson']
In [31]: age = [24, 35, 60]
In [32]: sex = ['man', 'women', 'man']
In [33]: users = {"name": names, "age":age, "sex":sex}
In [34]: df = DataFrame(users)
In [35]: df
Out[35]:
age name sex
0 24 youdi man
1 35 rino women
2 60 jackson man
这是定义一个 DataFrame 对象的常用方法——使用 dict 定义。字典的“键”("name","age","sex")就是 DataFrame 的 columns 的值(名称),字典中每个“键”的“值”是一个列表,它们就是那一竖列中的具体填充数据。上面的定义中没有确定索引,所以,按照惯例(Series 中已经形成的惯例)就是从 0 开始的整数。从上面的结果中很明显表示出来,这就是一个二维的数据结构(类似 excel 或者 mysql 中的查看效果)。
上面的数据显示中,columns 的顺序没有规定,就如同字典中键的顺序一样,但是在 DataFrame 中,columns 跟字典键相比,有一个明显不同,就是其顺序可以被规定,向下面这样做:
In [38]: df_1 = DataFrame(users, columns=['sex', 'name', 'age'])
In [39]: df_1
Out[39]:
sex name age
0 man youdi 24
1 women rino 35
2 man jackson 60
跟 Series 类似的,DataFrame 数据的索引也能够自定义
In [36]: df_1 = DataFrame(users, columns=['name', 'age', 'sex'], index=['I', 'II', 'III'])
In [37]: df_1
Out[37]:
name age sex
I youdi 24 man
II rino 35 women
III jackson 60 man
定义 DataFrame 的方法,除了上面的之外,还可以使用“字典套字典”的方式。
In [40]: books = {'name': {1:"python", 2:"golang" }, "price":{1:60, 2:100}}
In [41]: df_2 = DataFrame(books)
In [42]: df_2
Out[42]:
name price
1 python 60
2 golang 100
在字典中就规定好数列名称(第一层键)和每横行索引(第二层字典键)以及对应的数据(第二层字典值),也就是在字典中规定好了每个数据格子中的数据,没有规定的都是空。
DataFrame 对象的 columns 属性,能够显示素有的 columns 名称。并且,还能用下面类似字典的方式,得到某竖列的全部内容(当然包含索引):
In [43]: books = {'name': {1:"python", 2:"golang" }, "price":{1:60}}
In [44]: df_2 = DataFrame(books)
In [45]: df_2
Out[45]:
name price
1 python 60.0
2 golang NaN
In [46]: df_2 = DataFrame(books, index=[1,2,3])
In [47]: df_2
Out[47]:
name price
1 python 60.0
2 golang NaN
3 NaN NaN
DataFrame 对象的 columns 属性,能够显示素有的 columns 名称。并且,还能用下面类似字典的方式,得到某竖列的全部内容(当然包含索引):
In [48]: df_2.get("name")
Out[48]:
1 python
2 golang
3 NaN
Name: name, dtype: object
下面操作是给同一列赋值
In [54]: setattr(df_2, 'name', 'python')
In [55]: df_2
Out[55]:
name price
1 python 60.0
2 python NaN
3 python NaN
In [56]: df_2['price'] = 100
In [58]: df_2
Out[58]:
name price
1 python 100
2 python 100
3 python 100
也可以单独的赋值,除了能够统一赋值之外,还能够“点对点”添加数值,结合前面的 Series,既然 DataFrame 对象的每竖列都是一个 Series 对象,那么可以先定义一个 Series 对象,然后把它放到 DataFrame 对象中。如下:
In [59]: df_1
Out[59]:
sex name age
0 man youdi 24
1 women rino 35
2 man jackson 60
还可以更精准的修改数据吗?当然可以,完全仿照字典的操作:
In [59]: df_1
Out[59]:
sex name age
0 man youdi 24
1 women rino 35
2 man jackson 60
In [60]: df_1['age'][1] = 34
In [61]: df_1
Out[61]:
sex name age
0 man youdi 24
1 women rino 34
2 man jackson 60
差不多简单的介绍了,由于df很像sql,所以类似sql都可以处理,数据的聚合,分类等。
我前一段时间学习了numpy,pandas,matplotlib等一些数据处理的工具, 我当时也没有想过后面会使用到,就是看看。
现在回头看,有时候遇到问题,很多内容都忘记了, 不能及时的写出代码,但是我可以找出解决的方案。对知识的广度以及视野也会影响你的职业素养。
这件事,让我想起一个关于读书的讨论:
读书的意义是什么?
这个问题,就好比——你吃的美食最终都会变成糟粕,那你为什么还要吃呢?
书,和食物,不也很相似吗?
一个,因为好吃。
另一个,它们精华的部分会变成营养被你身体吸收,保证我们的成长,是潜移默化的
读书也是类似的,当时是看不出成效的, 量变达到一定时候,就会质变。