实现数据表格的目的
经常要在手机上通过pythonista练习python,因为Pythonista不支持pandas, 无法使用数据表格。很多时候需要用到数据表格,反复用字典和列表的组合显得很麻烦,所以就想按照pandas的思路,自己写一个简单的数据表格对象,把它当作一种学习。源代码公布在Github:
https://github.com/radiantbk/datasheet/blob/master/dsheet1.0.0.py
设计思路
作为一个数据表格,最基本的元素是row和col。利用字典和列表的特性,可以把列表作为row,每个列表元素作为一行,列的信息以字典的形式存在,每一个字典作为列表的元素,代表一行的数据。字典的键为字段名,键对应的值作为一个单元格的值。
dic1 = {'name':'a','age':20,'gender':'male'}
dic2 = {'name':'b','age':21,'gender':'female'}
dic3 = {'name':'c','age':18,'gender':'female'}
list1 = [dic1,dic2,dic3]
基于以上代码,每一个字典代表一行的数据,字典里的每一个键代表一个字段名,这构成了一个表格的雏形。作为一个表格对象,它需要有表格名、字段名,索引,数据;需要具备添加一列、添加一行、删除一行、删除一列等方法;字典列表的形式不便于阅读,最好能显示得想表格一样。所以这个对象应该有name(表格名)、colname(字段名)、表格内容等属性,索引在形成表格时作为表格的第一列添加。涉及的对象方法包括生成表格、更新字段名、增加一行、增加一列、截取部分表格内容、显示表格等方法。
实现过程
- 定义dsheet类,设置name,colname,ds(数据表)属性,并在创建dsheet对象时自动生成ds。代码如下:
class dsheet():
def __init__(self,data,col_name=''):
self.name=''
self.ds=[]
self.colname=col_name
self.__setds(data,col_name)- 定义__setds(self,data,col_name) ,data是要创建表格的值,可以是普通列表,也可以是二维列表。col_name是字段名,支持不整字段名,当字段名小于要创建的字段数量时,超出的部分字段名为空。无返回值,但是会更新ds内容,并触发字段名更新检查。代码如下:
def __setds(self,data,col_name):#不建议从外部调用
dic={}
dic_li=[]
if type(data) != list: #data必须为列表,否则创建失败,返回空值
print('data shoud be list')
return None
else:
for i in range(len(data)):
dic={'row':i}
dic_li.append(dic)
maxlen=1
for ea in data:
if type(ea)==list: #data可以是二维列表,当是二维列表时,生成多个字段
if len(ea)>maxlen:
maxlen=len(ea)
for i in range(len(dic_li)):
for ci in range(maxlen):
if type(data[i])==list:
try:
col = col_name[ci]
except:
col ='' #获取字段名失败时,将字段名相应的设置为空,确保字段列表不完成,仍然可以生成数据表
try:
coldata = data[i][ci] #获取某一个数据单元值失败时,将数据单元值设置为None,确保二维列表元素不完整时仍然可以生成数据表。
except:
coldata = None
dic={col:coldata}
dic_li[i].update(dic)
else:
try:
col = col_name[ci]
except:
col =None
dic={col:data[i]}
dic_li[i].update(dic)
self.ds = dic_li
self.__updatecol() #生成数据表后,检查字段名,更新字段名- 更新字段名。当DS有变化时,应该调用该方法,更新字段名。
def __updatecol(self):
if len(self.ds) == 0:
print('ds is empty, can not update column')
else:
collist = []
for ea in self.ds[0].keys():
collist.append(ea)
self.colname=collist- 在列的末端增加一列,addcol(self, name,data)。name为要增加的字段名,data是增加列的内容。data不需要是列表,如果不是列表,该列所有行的值都是一样是data的值。name不能为空,既然是增加,还是要给人家一个名分嘛。需要强调的是,data是列表时,如果长度小于行数,剩下的行的值会被设置为None。返回值为self,因此这个方法执行以后,会返回一个新的ds。代码如下:
def addcol(self,name,data):
dic={}
if type(data) != list:
for i in range(len(self.ds)):
dic={name:data}
self.ds[i].update(dic)
else:
for i in range(len(self.ds)):
try:
dic_data = data[i]
except:
dic_data = None
dic={name:dic_data}
self.ds[i].update(dic)
self.__updatecol()
return self- 在行的末端增加一行,addrow(self, data)。data是要增加的行数据,data 可以是列表也可以是一个单值。是单值时,增加行的个字段值均为data;是列表时,如果列表的元素个数小于字段数时,剩下的字段会被赋值为None。返回值为self,因此这个方法执行以后,会返回一个新的ds。代码如下:
def addrow(self,data):
dic={}
n=len(self.ds)
dic['row']=n
if type(data) != list:
for i in range(1,len(self.colname)):
dic[i]=data
else:
for i in range(1,len(self.colname)):
try:
dic_value = data[i-1]
except:
dic_value=None
dic[self.colname[i]]=dic_value
self.ds.append(dic)
return self- loc(row,col=’’),类似于pandas dataframe的loc方法,支持部分截取数据表内容,row代表的时要截取的行范围,":"代表所有行(默认值),"0:3"代表从第一行到第三行(以0为起点,右边不包含)。col代表要截取的字段范围,默认为空(代表截取所有字段的内容);如果col的值不为空,它必须是要截取的字段范围的列表(如:col=[‘name’,‘age’,‘gender’])。需要注意的是,如果col列表里的字段名有不存在与数据表的情况,会提示错误,返回None. 方法会返回一个新的ds,原来的ds不会改变。代码如下:
def loc(self,row,col=''):
row_range = ''
row_num=0
col_num=0
return_li=[]
return_info = 'row number showed:'+str(row_num)+', col number showed:'+str(col_num)
if col!='': #如果字段名的范围是部分,检查传入的字段名
if type(col)==list:
for ea in col:
if ea not in self.colname: #发现有字段名在数据表不存在,返回None
print('%s not in colname list'%ea)
print(return_info)
return None
else: #如果字段名不为空,又不是列表,提示错误返回None.
print('colname should be list, not %s'%(type(col)))
print(return_info)
return None
if row == ':':
#行的范围是全部时,确定字段范围,返回对应字典键的值,并将它存于一个新的列表里
if col =='':
col=self.colname
for ea in self.ds:
dic_loc={}
for ek in ea.keys():
if ek in col:
dvalue=ea[ek]
dic_loc[ek]=dvalue
return_li.append(dic_loc)
else:
#行的范围是部分,先选择行的范围,再确定字段范围,返回对应字典键的值,
#并将它存于一个新的列表里
row_range=row.split(':')
row_start = int(row_range[0])
row_end = int(row_range[1])
if row_start >=row_end:
print('start of row should be smaller than end of row')
return None
else:
if col=='':
col=self.colname
for n in range(row_start,row_end):
data = ''
dic_loc={}
for ek in col:
dic_dsn=self.ds[n]
dvalue = dic_dsn[ek]
dic_loc[ek]=dvalue
return_li.append(dic_loc)
new_data=[]
new_col=[]
for ec in return_li[0].keys():
new_col.append(ec)
for ed in return_li:
new_value=[]
for ek in ed:
new_value.append(ed[ek])
new_data.append(new_value)
new_ds = dsheet(new_data,new_col)
return new_ds- show(row,col=’’)方法,参数及代码和loc方法类似,它仅仅返回选定的数据表范围包含多少行和多少列。它的作用是通过print函数显示数据表的选定内容。代码如下:
def show(self,row,col=''):
row_range = ''
row_num=0
col_num=0
return_li=[]
return_info = 'row number showed:'+str(row_num)+', col number showed:'+str(col_num)
if col!='':
if type(col)==list:
for ea in col:
if ea not in self.colname:
print('%s not in colname list'%ea)
return return_info
else:
print('colname should be list, not %s'%(type(col)))
return return_info
col_range= len(self.colname)
if row == ':':
if col =='':
col_num=len(self.colname)
head = ','.join(self.colname)
col=self.colname
print(head)
else:
col2=col.copy()
col_num=len(col)
head=','.join(col2)
print(head)
for ea in self.ds:
data=''
for ek in ea.keys():
if ek in col:
dvalue=ea[ek]
data += str(dvalue)+','
data=data[:-1]
row_num += 1
print(data)
else:
row_range=row.split(':')
row_start = int(row_range[0])
row_end = int(row_range[1])
if row_start >=row_end:
print('start of row should be smaller than end of row')
return None
else:
if col=='':
col_num=len(self.colname)
head = ','.join(self.colname)
col=self.colname
print(head)
else:
col_num=len(col)
col2=col.copy()
head=','.join(col2)
print(col2)
print(head)
for n in range(row_start,row_end):
data = ''
for dkey in col:
data += str(self.ds[n][dkey])+','
data=data[:-1]
row_num+=1
print(data)
return_info = 'row number showed:'+str(row_num)+', col number showed:'+str(col_num)
return return_info试运行代码
- 运行创建代码,并运行show()。
l1=['beijing','shanghai','guangzhou','shenzhen']
ds1 = dsheet(l1,['city'])
print(ds1)
输出结果如下:
<__main__.dsheet object at 0x000001F7789823C8>
>>>
>运行如下代码:
>if __name__=='__main__':
l1=['beijing','shanghai','guangzhou','shenzhen']
ds1 = dsheet(l1,['city'])
ds1.show(':')
输出结果如下:
row,city
0,beijing
1,shanghai
2,guangzhou
3,shenzhen
>>> - 运行addrow,addcol方法,看看增加行和列的效果
if __name__=='__main__':
l1=['beijing','shanghai','guangzhou','shenzhen']
ds1 = dsheet(l1,['city'])
l2=[1,2,3,4]
ds1=ds1.addcol('rank',l2)
ds1.show(':')
输出结果如下:(增加了一列rank)
row,city,rank
0,beijing,1
1,shanghai,2
2,guangzhou,3
3,shenzhen,4
再增加一行,代码如下:
if __name__=='__main__':
l1=['beijing','shanghai','guangzhou','shenzhen']
ds1 = dsheet(l1,['city'])
l2=[1,2,3,4]
ds1=ds1.addcol('rank',l2)
l4=['chengdu','5']
ds1=ds1.addrow(l4)
ds1.show(':')
输出结果如下:
row,city,rank
0,beijing,1
1,shanghai,2
2,guangzhou,3
3,shenzhen,4
4,chengdu,5
>>> 运行loc方法,部分选择数据表内容,检查loc方法是否会生成一个新的ds
再增加一列GDP,通过loc方法选择第二行和第三行的GDP和City.同时检查loc方法是否改变了ds1的内容。
if __name__=='__main__':
l1=['beijing','shanghai','guangzhou','shenzhen']
ds1 = dsheet(l1,['city'])
l2=[1,2,3,4]
ds1=ds1.addcol('rank',l2)
l4=['chengdu','5']
ds1=ds1.addrow(l4)
l5=[2400,2900,2100,2200,1200]
ds1=ds1.addcol('GDP',l5)
ds2=ds1.loc('1:3',['city','GDP'])
ds2.show(':')
ds1.show(":")
输出的结果如下:
row,city,GDP
0,shanghai,2900
1,guangzhou,2100
row,city,rank,GDP
0,beijing,1,2400
1,shanghai,2,2900
2,guangzhou,3,2100
3,shenzhen,4,2200
4,chengdu,5,1200
>>> 发现ds2是一个独立于ds1的表格,它不会改变ds1的内容。总结
以上代码初步实现了数据表的基本功能,但是还有很多功能需要进一步完善,例如:删除行或列(当然可以通过loc方法来实现,但最好能有一个独立简单的方法)、分类统计(类似于dataframe groupby),等将在下一篇博文里分享。
版权声明:本文为weixin_42101791原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。