一、 案例简介
下面有2份文件,是某产品1月和2月的销售数据,我们需要对文件数据进行处理分析,计算每日的销售额并以柱状图表的形式进行展示。
(文件链接: https://pan.baidu.com/s/1FWQhkToiJT_xZfYGfIWyvQ?pwd=a2hi 提取码: a2hi )
文件数据格式如下图所示,1月销售数据是普通文本,使用逗号分割数据记录,从前到后分别是(日期,订单 id ,销售额,销售省份),2月销售数据是 JSON 数据,从前到后也是(日期,订单 id ,销售额,销售省份)。
回忆我们在综合案例---数据可视化---折线图、地图、柱状图中处理数据的步骤,现在我们要融合面向对象的思想去处理数据,思路如下:
二、读取数据与封装数据对象
2.1列表
我们要把读取处理完的数据放在一个(元素类型为类对象的)列表中,对象封装在类中,这个类对象的类命名为Record,对应操作: 新建模块"Record_define.py",定义类"Record",内容如下:
python
class Record:
def __init__(self,date,order_id,money,province): #运用__init__构造方法,便于给类对象赋值
self.date=date #订单日期
self.order_id=order_id #订单ID
self.money=money #订单金融
self.province=province #销售省份
def __str__(self): #运用__str__构造方法,便于控制类对象转换为字符串
return f'{self.date},{self.order_id},{self.money},{self.province}'
2.2读取数据
我们先定义一个抽象类FileReader用来做顶层设计,确定有哪些功能需要实现,需要实现成员方法read_data( ),用来读取文件的数据,将读到的每一条数据都转换为Record对象,将它们都封装到列表record_list内返回。
文件"2011年1月销售数据.txt"和"2011年2月销售数据JSON.txt"格式不同,需要具体情况具体分析。父类FileReader已经明确了需要有哪些功能实现,具体的方法实现,由子类去实现。
子类TextFileReader 处理文件"2011年1月销售数据.txt",其以普通文本的格式存储数据,每行存储每日一个省份的销售数据,我们以readlines( )方法 读取文件(按照行的方式把整个文件中的内容一次性全部读取,返回一个列表,其中每一行的数据为一个元素),再使用split( )方法(字符串分割方法),把逗号作为分隔符,将订单日期,订单ID,订单金额,订单省份分隔开后放入类对象中,再把类对象放入列表中, 数据初步处理完成。
子类JSONFileReader处理文件"2011年2月销售数据JSON.txt",其以JSON格式存储数据,每行也是存储每日一个省份的销售数据。我们读取文件后需要先转换格式(将JSON格式数据转换为python数据格式),通过字典数据访问方式将订单日期,订单ID,订单金额,订单省份放入类对象中,再把类对象放入列表中, 数据初步处理完成。
对应操作: 新建模块"File_define.py",先定义抽象类"FileReader",再由子类TextFileReader和JSONFileReader具体实现对两种不同类型数据的处理,内容如下:
python
import json
from Record_define import Record
# 先定义一个抽象类用来做顶层设计,确定有哪些功能需要实现
class FileReader:
def read_data(self)->list[Record]:
#读取文件的数据,将读到的每一条数据都转换为Record对象,将它们都封装到list内返回
pass
#处理普通文本类型
class TextFileReader(FileReader):
def __init__(self,path):
self.path=path #定义成员变量记录文件的路径
#复写(实现抽象方法)父类的方法
def read_data(self) ->list[Record]:
#打开文件
f=open(self.path,'r',encoding='UTF-8')
#创建类型为Record类对象的列表以存储数据
record_list:list[Record]=[]
for line in f.readlines():
line= line.strip() # 消除读取到的每一行数据中的\n
list=line.split(',')
record=Record(list[0],list[1],int(list[2]),list[3]) #必须将类Record中第三个成员变量强制转换为int类型,否则后面无法正常累加销售额
record_list.append(record)
f.close()
return record_list
#处理json类型
class JSONFileReader(FileReader):
def __init__(self,path):
self.path=path #定义成员变量记录文件的路径
# 复写(实现抽象方法)父类的方法
def read_data(self) ->list[Record]:
f=open(self.path,'r',encoding='UTF-8')
record_list:list[Record]=[]
for line in f.readlines():
dict_line=json.loads(line)
record=Record(dict_line['date'],dict_line['order_id'],int(dict_line['money']),dict_line['province'])#必须将类Record中第三个成员变量强制转换为int类型,否则后面无法正常累加销售额
record_list.append(record)
f.close()
return record_list
#仅供测试使用,找bug
if __name__=='__main__':
text_file_reader = TextFileReader("E:/可视化案例数据/2011年1月销售数据.txt")
json_file_reader = JSONFileReader("E:/可视化案例数据/2011年2月销售数据JSON.txt")
list1 = text_file_reader.read_data()
list2 = json_file_reader.read_data()
for l in list1:
print(l)
for l in list2:
print(l)
注意:
- 在定义类TextFileReader中成员方法read_data( )时,有一行代码" line= line.strip() ",这是因为文件"2011年1月销售数据.txt"内每行数据的最后都有一个换行符,如果不去掉,换行符会被放入列表record_list中,作为一个个单独的元素被存储着,干扰后序数据处理,所以需要用strip( )方法消除读取到的每一行数据中的换行符"\n"。
- 在将数据放入列表record_list前,即创建类对象record并传参时,一定要将订单金额强制转换为int类型,这样在模块"text.py"中计算当日总销售额时才能正常累加,如果忽略了这一步,销售额累加就会像字符串拼接一样,例如原本1+2=3,忽略了这一步就会变成1+2=12,会造成机械地拼接。
三、计算数据
把类设计完成后,新建模块"test.py",开始真正读取文件,将2个月份的数据合并为1个列表all_data来存储,然后计算数据,即求每日各个省份的销售额相加,步骤如下:
- 首先准备一个字典,用来存储计算后的数据,其中key为日期,value为当日总销售金额(生成图表所需要的数据就是日期和当日总销售金额)。
- 利用for循环,把列表all_data中每日各个省份的销售金额进行叠加。
代码如下:
python
from File_define import TextFileReader, JSONFileReader
from Record_define import Record
#读取并处理数据
text_file_reader=TextFileReader('E:/可视化案例数据/2011年1月销售数据.txt')
json_file_reader=JSONFileReader("E:/可视化案例数据/2011年2月销售数据JSON.txt")
jan_data:list[Record]=text_file_reader.read_data()
feb_data:list[Record]=json_file_reader.read_data()
# 将2个月份的数据合并为1个list来存储
all_data: list[Record] = jan_data + feb_data
#计算数据,即求每日各个省份的销售额相加
data_dict={} #创建字典,存储计算后的数据,日期:金额
for record in all_data:
if record.date in data_dict.keys():
#当前日期已有记录,和老记录叠加
data_dict[record.date]+=record.money #修改字典中key对应的value
else:
data_dict[record.date]=record.money #新增字典元素
注意:for循环中的临时变量record是一个类对象。
四、绘图
处理完数据后开始绘图,X轴数据应为日期,我们需要把从字典中取出的全部日期数据转换成列表类型,以便程序正常运行,Y轴数据为当日销售金额,同理转换成列表类型。
python
from pyecharts.charts import Bar
from pyecharts.options import *
#绘制图表
bar=Bar()#这样写也是在构建类对象
bar.add_xaxis(list(data_dict.keys())) #添加X轴数据,将从字典中取出的全部日期数据转换成列表类型
bar.add_yaxis('销售额',list(data_dict.values()), label_opts=LabelOpts(is_show=False)) #添加Y轴数据,将从字典中取出的全部销售额数据转换成列表类型
bar.set_global_opts(
title_opts=TitleOpts(title="每日销售额")
)
bar.render("每日销售额柱状图.html")
运行代码后生成的图表:
模块"text.py"的完整代码如下:
python
from File_define import TextFileReader, JSONFileReader
from Record_define import Record
from pyecharts.charts import Bar
from pyecharts.options import *
from pyecharts.globals import ThemeType
#读取并处理数据
text_file_reader=TextFileReader('E:/可视化案例数据/2011年1月销售数据.txt')
json_file_reader=JSONFileReader("E:/可视化案例数据/2011年2月销售数据JSON.txt")
jan_data:list[Record]=text_file_reader.read_data()
feb_data:list[Record]=json_file_reader.read_data()
# 将2个月份的数据合并为1个list来存储
all_data: list[Record] = jan_data + feb_data
#计算数据,即求每日各个省份的销售额相加
data_dict={} #创建字典,存储计算后的数据,日期:金额,例如:{"2011-01-01":1689,"2011-01-02":1532,......}
for record in all_data:
if record.date in data_dict.keys():
#当前日期已有记录,和老记录叠加
data_dict[record.date]+=record.money #修改字典中key对应的value
else:
data_dict[record.date]=record.money #新增字典元素
#绘制图表
bar=Bar()#这样写也是在构建类对象
bar.add_xaxis(list(data_dict.keys())) #添加X轴数据,将从字典中取出的全部日期数据转换成列表类型
bar.add_yaxis('销售额',list(data_dict.values()), label_opts=LabelOpts(is_show=False)) #添加Y轴数据,将从字典中取出的全部销售额数据转换成列表类型
bar.set_global_opts(
title_opts=TitleOpts(title="每日销售额")
)
bar.render("每日销售额柱状图.html")
五、总结
用面向对象思想去实现数据可视化最大的特点就是:把具体处理文件数据的操作步骤封装在类中,使用时直接调用。
从本案例中不难看出面向对象的3 大特性:封装、继承、多态。
- 把具体处理文件数据的操作步骤封装在类中,可供多次调用。
- 在模块"File_define.py"中先定义了一个抽象类FileReader,用来做顶层设计,FileReader作为父类,其中成员方法read_data()并不进行详细定义,而是在子类TextFileReader和JSONFileReader去详细定义了read_data(),即进行了复写。
- 同样是源自父类FileReader,在模块"test.py"中调用处理1月销售数据和2月销售数据时,得到了不同的数据,同样的行为(函数),传入不同的对象,得到不同的状态,这体现了"多态"。