Python 如何优雅地使用 pandas

如何优雅地使用 pandas 对数据进行整理-1 (数据类型介绍:变量,列表,字典,数据框)

为什么要学会使用 pandas ?
我觉得答案是:将数据结构化,以便更好地管理

而 pandas 你可以理解为 利用代码 实现你手动对 Excel 表格处理的功能
实际上,pandas 能实现的功能,基本在 Excel 上都能完成
两个之间只是一个 “时间效率” 和 “一劳永逸” 的事情
习惯用哪种是个人的一种选择罢了

数据类型

在处理数据时,首先我们得明白数据长什么样子,我们期望输入的数据长什么样子。

这一块的理解我其实并不太到位,日后随学习的深入再更。
在这里我们先说明一些无关紧要的概念

Python 的基本数据类型 (我不知道专业的人是不是这么讲)

  • 变量 variable,列表list,字典dict,数据框dataframe
  • 这里只讲 “他们是什么” 和 “怎么构造”,其他细节的操作以后有空再写

变量:

  • 这里的变量不是统计学的概念,你可以将其理解为存储某个数据的词

  • 需要注意的是,变量可以随意命名,但是要按遵守以下规则:

    1. 变量名可以包括字母,数字和下划线 (_) ,(那就是说,其他奇怪的符号都不可以)
    2. 变量名不能以数字开头。如:day1, day_1 是对的,1day 就不可以
    3. Python 里固有的函数、特殊含义字符,不能作为变量名。如:if, True 等。python 常见内置函数
a = 2         ## 整数型数字变量
b = 3.56      ## 浮点型数字变量
one = 'John'  ## 字符变量
r9A = True    ## 布尔型变量

##### 需要注意的是
##  1. 变量名可以包括字母,数字和下划线 (_)
##  2. 变量名不能以数字开头

  • 但是,我们必须优雅,所以,以上的变量名都是不雅的示范
    • 做了一名优秀的程序员,应该做到:看到变量名,就知道这个变量指代的是什么,如果你一直以 a, b, c 等简单的命名,不用一个月,两三天后你就会忘记自己写的是什么了,这对于代码的维护十分不利。
    • 所以下面推荐两个我感觉比较好用的优雅命名法
    1. 下划线续命,比如:
      • A_num ,可以用来表示字符串中“A”的个数。
      • diff_word_list,可以用来表示一个“装不同单词”的列表
      • 实际上,用多少个下划线(),用不用缩写(num, diff),也是全凭个人习惯,在优雅的同时得自己用的舒服。建议不要超过两个下划线(),太长的话实在太难看了,自己能看懂就行,能短就短。
    2. 驼峰命名法 Camel-Case - (是不是感觉更高雅了)
      • 实际上,有些人不喜欢下划线
      • 驼峰命名发就是将 多个单词 拼接在一起形成一个整体作为变量名,而区分开不同的单词就是大小写,如:
      • myFisrtName, differentWordList
      • 一般来说,第一个单词小写,往后每接一个单词都要首字母大写。据说这样可以提高程序的可读性,至于是不是这样,那就见仁见智啦。
    3. 匈牙利命名法 - (一看就觉得很厉害)
      • 基本原則是:变量名 =属性 型別 物件描述
      • 这种一般是大佬命名法,需要写巨型程序时比较有优势,对于普通人来说,前两种任选一种就足够了

列表 list

  • 列表的构造非常简单,[] 中括号,逗号分割

  • 列表,实际上可以理解为一个大的快递柜

    • 它非常的宽容,你扔什么进去,它就能存什么。
    • 而且它不挑,无论是重复的元素,还是不同类型的变量,它都能接纳
  • 列表是有序的

    • 宽容,不代表没有底线
    • 实际上,列表很有自己的规则,里面存放的元素都是乖乖排队站好,而且每个都有自己的标号。(想要取出某个元素就像你知道货架号去取快递一样)
num_list = [1,8,6,1,7]       ### 这是一个存放数字的列表
mixList = [1,'panda','4',9]  ### 这是一个混合元素的列表
list2_in_list1 = [[1,8,6,1,7],[1,'panda','4',9]]  ### 列表里面也能放列表
dict_in_list = [{'a':30},]   ### 当然存个字典也不是什么问题

### 取出元素,本来不想讲的,还是提一下吧
### 比如说上面的 num_list, 我想取第二位 ‘8’
### 则:

num_list = [1,8,6,1,7]
target = num_list[1]      ## 用中括号去,Python 从0可以计数,所以是[1], 这个也叫 列表的切片

  • 然而,大多数情况下,不会有一个列表傻傻的站在那里等你。
    • 更多的情况是,把列表当做一个容器,装你需要的东西。
    • 装完以后再根据自己的需求,取列表中的元素。(切片)
    • 所以列表的构建常常和循环(for)、判断(if)搭配使用。

例一:找出100内的所有质数

  • 我们可能需要用到额外的东西:
    - for / while 循环
    - if 判断
    - .append() 将元素添加到列表
    - 可能还需 range(), len() 帮助你执行循环的好东西
### 比如说,我想取出100内所有质数构成列表
### 首先分析问题,质数的定义是:大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。 
### 判断一个数是否为质数 只要依次除以 所有比自身小的整数,只要如果商是整数只有一个(除1得到它本身),那这个数就是质数。如果不只一个整数商,那就是素数。


all_num = range(1,101)  
## range()函数,获取一个 1 - 100 的列表, (其实要 list(range(1,101)才是我们熟悉的列表的样子,range()只是一个函数)
## 为啥是 101,如果是100,因为 Python 从0开始取,取100个数字就到99
## 现在我们从1开始取,要取100个数,当然要到101

prime_num = []         ## 新建一个空列表

for i in all_num:     
    test_num = i      ## 依次取出 1 - 100
    num = 0           ## 用于记录出现整数商的个数,因为质数的整数商应该只有一个,那就是它本身(除以1)              
    for j in range(1,test_num): ## 还是从1开始,取到比这个数自身小1
        quotient = test_num/j    ## 求商,看商是否为整数,如果是,说明这个数不只一个因数,那他就不是因素
        if quotient in list(all_num): ## 可能的整数都在 all_num 里
            num += 1       ## 如果是整数,记录数 +1
    if num == 1:      ## 如果商只有一个整数,那说明这是个质数
        prime_num.append(i) ## 将这个数添加到质数的列表中

print(prime_num)      ## 打印出来看看结果对不对

#--------------------------------结果————————————————————————
#[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

### 思考一下,为什么 num 为什么要放在第一个循环里面?
### 以上只是我的思路,不一定是最优的方法
### 实际上,也不用除比自己小的数,只要比自己二分之一小的数就足够了,以为除比自己二分之一大的数肯定是小于2 (自己想想吧,代码我不写了)

好吧,其实我给的只是一个无聊的例子。在实际应用中,要根据自己数据的特点,提取需要的数据,这就是为什么一开始说的,你要对自己的“数据长什么样子,希望整理后的数据长什么样子”,有个把握。然后利用循环判断,将元素添加( .append() )到列表中,构建符合目的的列表。

例二:找出子目录下所有的 .csv 文件,打开并逐行打印

  • (为什么要逐行打印?) - 因为你能逐行取出数据,说你可以随心所欲地对数据进行加工处理,比如“加减乘除”。
  • CSV 文件说明一下,这是一种用逗号分割的文本文件。可以直接用 Excel 打开,Excel 也可以直接保存工作表为 csv 格式文件,这意味着,我们可以配合着 Excel 使用,因为 Excel 在可视化数据时具有独特的优势。
    1. 我们可以在 Excel 中预处理数据,如 “手动数据数据”,再放到 Python 中用 pandas 做进一步的加工
    2. 也可以用 pandas 处理好数据后保存为 csv 文件,然后用 Excel 打开查看结果。
    3. 一图胜前言,请看


      关于 csv 文件的介绍.png
  • 我们可能需要用到额外的东西:
    - os 调用系统一个库
    - if string.endswith(): 判断文件是否以 ‘’.csv‘’ 结尾。同理,判断是否以 “xxx” 开头,应用 string.strartswith()
    - .append() 将元素添加到列表
    - 可能还需 range(), len() 帮助你执行循环的好东西
    - pandas 打开,处理及保存 csv 文件 (提前用到,后面会细讲)
import os
import pandas as pd   ### 简写,之后就不用敲 "pandas" 那么多字符,直接敲 "pd" 就好


filePath = '/User/data'     ### 假设我的数据杂乱的放在这个目录(文件夹)下
fileList = os.listdir(filePath)  ### 返回一个列表,这个列表包括这个目录下所有的文件以及文件的名字,要注意,是名字,不是路径。

csvFileList = []   ### 新建一个列表用于存放csv文件路径
for i in fileList:
    if i.endswith('.csv'):  ### 找出.csv 后缀的文件
        file =  filePath + '/' + i  ### os.listdir() 获取到只是名字,要自己构造成完整的路径
        csvFileList.append(file)

for csv in csvFileList:      ### 依次打开 csv
    data = pd.read_csv(csv)   ### 调用 pandas 里面的 read_csv() 读取 csv 文件内容,
                             ###实际上,直接 a = open(csv,'r') 也能打开,不过那是一个文本,
    dataframe = pd.DataFrame(data)  ### 用 pandas 的.DataFrame() 将数据装为 **数据框**, 这个是这一篇文章像将的重要数据结构
    print(frame.ix[0])        ### ix[var1,var2] 第一个是行,第二个是列,后面会细讲。 这里的意思是取第一个除了打印,记住 Python 从0开始

####### 后记说明
### 一般来说,我们用 Excel 整理数据时,每一列,都有一个列表,即第一行,是列名。如:“姓名”,“年龄”,“分数”。
### 所以在我们取数据时,其实取的是第二行的数值,但pd.read_csv()其实是默认第一行为 header(列名),并默认加上 index(行号)
### 如果第一行不是 header 可以手动关闭
#   data = pd.read_csv(csv,header=None) ##注意None,不是 False
### 如果想保存为新的 csv
#   dataframe.to_csv(save_path)  ## save_path 是存在的文件路径

字典 dict

  • 字典的形式是: a = {‘key’:’value’} ; ( {}大括号,key键 和 value值 一一对应)
  • 在列表中,我们是根据元素所在的位置来取出元素 (知道货架号去拿快递)。但是在字典里,每个元素都有一个对应的“钥匙” (这时相当于你那取货码去快递柜取快递一样)
  • 字典是无序的,但是 key 和 value 是一一对应的。正是因为这样,所以有无顺序并不重要。反正 key 是唯一的,随时能找到 value。
  • 因为 key 和 value 是一一对应的,所以一个字典里,key 的取值是唯一的,value 不一定,可以重复。(就像我的取货码key只能取自己的快递,但是我们可以买一样的东西 value)
  1. 构建字典的方法:
########## 第一种(推荐)
a_dict = {'apple':5}  ### apple 对应的值是5
a2_dict = {'apple':'five'}
b_dict = {'apple':[4,8,5]}  ### apple 对应一个列表,比如说3个人,各自有4,8,5 个苹果
c_dict = {'apple':{'big':5,'small':6}} ### apple 对应一个字典(这种叫嵌套字典)

# 字典构造的格式很简单,大括号{},里面是{'key1':value1,'key2':'value2'} 
# 以上三个例子展示了字典的可变性,value 可以是变量(数字、字符串)、列表和字典,其中列表、字典里面有可以继续嵌套,但是这里温馨提示一句,字典嵌套太多很容易乱,而且代码的效率低(嵌套多,循环多),所以最好还是想办法简化你的程序。

#------------------------------------------------------------#

############ 第二种 在已有字典的情况下添加 (实用)
a_dict = {'apple':5}  
a_dict['peach'] = 8

## 这时候的 a_dict = {'apple':5,'peach',8}

#------------------------------------------------------------#

############ 第三种 使用 dict.setdefault()  (推荐)
a_dict.setdefault('banana',67) ## 第一次参数是 key, 第二个参数是预设的默认值

## 实际上,如果直接 写a_dict.setdefault('banana'),返回的是 None, 因为原本的 a_dict 里面没有 'banana' 这个键

#------------------------------------------------------------#

############ 第四种 用 dict() 函数转换 双值子序列
# 什么是 双值子序列 ?
# 如:letter=[['a','b'],['c','d'],['e','f']]
# 像这样两两配对好的 列表、元组
# 如:[('a','b'),(c','d'),('e','f')] , ['ab','cd','ef'] 这些都是

letter=[['a','b'],['c','d'],['e','f']]
l_dict = dict(letter)

## 这个就相当于l_dict2, 第一个元素为 key, 第一个元素为 value
l_dict2 = {'a':'b','c':'d','e':'f'}  

  1. 如何取字典里面的东西
a_dict = {'apple':5,'peach',8}
### 取键
key = a_dict.keys()    ### 结果:dict_keys(['apple','peach']),返回的并不是列表,而是一个对象
key = list(key)        ### 结果['apple','peach'],好了现在是列表的

# 一般情况下,取键和循环搭配使用

for k in a_dict.keys():
    key = k
    print(key)           ### 结果程序依次打印,'apple','peach'

### 取值,同理,.values()

### 键 和 值 一起取 .items()
for k,v in a_dict.keys():
    key = k
    value = v
    print(key)
    print(value)         ### 依次打印'apple','peach','5','8'

  1. 在更多的时候,我们并不会动手完完整整的码一个字典,太累了
    所以,我们需要一些优雅的技巧

例一,如何合并并将两个等长的列表(list)转为字典(dict)

  • 为什么要合并两个等长的字典?
  • 很简单的一个例子,学生成绩,学生名单一个列表,成绩一个列表,但是两者一一对应。(其他情况自己类别使用)
### 第二种字典构造方法 dict() 排上用场啦

name = ['xiaoming','zhubajie','shadiao','xiaoming2'] ## 字典 key 是唯一的,如果重命了要想办法区分开来
score = [90,88,45,100]

studentScore = dict(zip(name,score))  

## zip() 函数将对象中*对应的*元素打包成一个个元组,返回一个对象。
## dict() 对这个对象解析,构成一个新的字典

print(studentScore)

#----------------结果
# {'xiaoming': 90, 'zhubajie': 88, 'shadiao': 45, 'xiaoming2': 100}

例二,如何是 dict.setdefault() 构建较复杂字典

  • 很多时候,我们不是提前知道 key 和 value。而是在循环的过程中,找到目的值,再代入 key 和 value。
  • dict.setdefault(key,default=None) 接收两个参数,第一个参数是健的名称,第二个参数是默认值。如果字典中包含有给定键,则返回该键对应的值,否则返回为该键设置的值。
  • (之前处理数据时真的遇到过一些经典的例子,但是在写的时候想不起来,想到再更吧,一般来说,直接用 dict[key] = value 没毛病,但是也存在只能用 .setdefault() 预设默认值的情况)

### 1. 常规操作
studentScore = {'xiaoming': 90, 'zhubajie': 88, 'shadiao': 45, 'xiaoming2': 100,'surperman':1000,'kate':56,'sanman':92,'keiven':73,'xijinping':99,'caixukun':44,'wangzhe':66,'king':89}

## 比如说,我们想把所有 k 开头的同学成绩取出来构成一个新的字典

KstudentScore = {}      ### 先建一个空白字典备用

for k,v in studentScore.items(): ## 依次取出 键,值
    name = k
    score = v   ## 其实直接用k,v也行,但我觉得这样更优雅,哪怕过一年你也知道自己在干嘛
    if name.startswith('k'):
        KstudentScore.setdefault(name,score) 
print(KstudentScore.setdefault)

## 或者 KstudentScore[name] = score 也是行的
## ---------------- 结果 ---------------------- 
## {'kate': 56, 'keiven': 73, 'king': 89}

##############################################################

### 2.key 对应的值是一个列表 list (字典同理)
### 比如说,每个同学有三次成绩,放在三个字典里(具体的我不写了)

## 那么第一个粗暴的方法就是我提前把说有的键值 key都准备好,对应一个列表[],到时候找到每找到一个同学,.append()添加进去就行啦

allScore = {'xiaoming':[],'laoli':[],'zhubajie':[]}  ## 假设全班就三个人
for k1,v1 in studentScore1.items():
    allScore[k1].append(v1)
for k2,v2 in studentScore2.items():
    allScore[k2].append(v2)
for k3,v3 in studentScore3.items():
    allScore[k3].append(v3)

## 完全没毛病
## 但是我们要优雅,所以

allScore2 = {}
score = [studentScore1, studentScore2, studentScore3]
for scoreDitc in score:
    for k,v in scoreDitc.items():
        allScore2.setdefault(k,[]).append(v)  ## 直接构建字典并添加值到对应的列表

#---------------幻想中的结果
# {'xiaoming':[97,49,79],'laoli':[87,56,38],'zhubajie':[100,87,97]}


#### 如果要嵌套字典,原理一样,格式为
dict.setdefault(key,{})['key2']=value

由于每个人需要处理的数据都不尽相同,我也不能举出十全十美的例子,大家根据实际情况选择合适的工具使用。我感觉自己也没有讲清楚,如果有什么疑惑或建议,欢迎联系。

数据框 dataframe (终于到重点了)

  • 数据框最通俗的理解就是一张 Excel 的工作表
  • 为什么要学这种数据类型,因为这种是我们看着舒服的数据类型。(上图吧)
    • 比如说我有一批水果的数据:(随便在 Excel 上面码了一些数字不要在意细节),这个就是一个 dataframe,所有的信息都非常清晰


      用 Excel 打开 csv 文件后现实的内容 .png
  1. 如何构建 dataframe
### 1. 最常见的其实是我们直接读取原本就已经构建好的 csv 文件
data = pd.read_csv('/User/shop/fruit.csv') #读 csv 文件
df = pd.DataFrame(data)                    #转为数据框
print(df)     #很多人不可看看结果是不会心死的

#----------------------结果
#      student  score
# 0   xiaoming     84
# 1   xiaodong     93
# 2  wujingtao    100
# 3   superman      0


# 前面也说了,pd.DataFrame() 会默认把第一行作为 header,并且会默认添加 index (就是上面你看到左边的 0123),如果不想用,可以禁掉,我们看看效果 

data2 = pd.read_csv('/User/shop/fruit.csv',header = None) # 注意是 None,不是 False 
df2 = pd.DataFrame(data2)
print(df2)

#----------------------结果
#            0      1
# 0    student  score
# 1   xiaoming     84
# 2   xiaodong     93
# 3  wujingtao    100
# 4   superman      0
#
# 结果就是 会用数字作为你的 header 
# 为什么需要 header 和 index,就是为了方便你取值 (另一篇再讲吧)

#------------------------------------------------------------#

### 2. 将字典转为 DataFrame  (这个也很常用,上面是读取,这个是输出)
### 还记得上面三次考试成绩吗
### {'xiaoming':[97,49,79],'laoli':[87,56,38],'zhubajie':[100,87,97]} 我还是拿过来吧
### 现在我们想 优雅地 将这些数据转成 datafame,怎么做呢?


studentScore1 = {'xiaoming':97,'laoli':87,'zhubajie':100}
studentScore2 = {'xiaoming':49,'laoli':56,'zhubajie':87}
studentScore3 = {'xiaoming':79,'laoli':38,'zhubajie':97}
### 还是得准备些数据才能讲得清

dempDict = {} ##先创建个临时的空白字典备用
score = [studentScore1, studentScore2, studentScore3] ## 我们还是假设有三次成绩
examNum = 0
for scoreDitc in score:
    examNum += 1  ##记录考试场数
    name_list = [] ## 创建一个文件存学生名字,思考一下为什么要这么做
    for name,score in scoreDitc.items(): ##这时我们需要分门别类的将数据放好
        if name not in name_list:
            name_list.append(name)
        else:
            pass
        dempDict.setdefault('Exam{}'.format(examNum),[]).append(v) ## .format 是一个字符串格式化方法非常棒,我放另一篇里面讲
    dempDict['name'] = name_list  ## 最后将学生信息也存进 dempDict里面
df = pd.DataFrame(dempDict) 
print(dempDict)
print('\n')
print(df)


#----------------------结果
#{'Exam1': [97, 87, 100], 'name': ['xiaoming', 'laoli','zhubajie'], 'Exam2': [49, 56, 87], 'Exam3': [79, 38, 97]}

#    Exam1      name  Exam2  Exam3
# 0     97  xiaoming     49     79
# 1     87     laoli     56     38
# 2    100  zhubajie     87     97

##### 1. 首先思考两个事情:(1) 为什么 dempDict = {} 是放在最外面的,放在循环里面可以吗? (2) 为什么name 要额外处理

比较重要的说明我还是放在外面吧
1. 首先,这种方法构建的时候,列表必须是等长的,如上面的列表全是都是3个元素。如果不等长会怎么样?抱歉会报错。如果真的缺数据怎么办,(比如有人缺考了),那就想想办法补齐,比如说补 0 或 空格。上面这个例子不好改,因为这里例子里面即使缺考,也必须会有一个值,否则就构不成字典,但是实际中你可以会遇到其他情况,这时可以选择用 .append(0) 或 .append(‘’) 代替,使得列表(list)等长
2. 敏锐的你一定注意到,dempDict 里面的每一个 键 key,就是对应 df (dataframe) 里面的 header
3. 但是也应该观察到,dempDict 是无序的,但是 df (dataframe) 里面的 header 的顺序是和 dempDict 保持一致的。这种不符合我们的期待,因为我们更想名字放在第一列,那要怎么改呢?

### 自定义 dataframe 的 header

scoreDict = {'Exam1': [89, 89, 89], 'name': ['xiaoming', 'laoli','zhubajie'], 'Exam2': [89, 89, 89], 'Exam3': [89, 89, 89]}   ## 偷懒直接 copy 了

df = pd.DataFrame(dempDict,columns = ['name','Exam1','Exam2','Exam3'])     ## 好吧其实加个columns就好了
print(df)

#----------------------结果
#        name  Exam1  Exam2  Exam3
# 0  xiaoming     97     49     79
# 1     laoli     87     56     38
# 2  zhubajie    100     87     97
#
## 漂漂亮亮哒

##### 如何保存? 如何将 dataframe 保持为 csv 文件

df.to_csv(sava_path)   ### 很简单的一句话,sava_path是你要放的路径,包含文件名,如'/User/output/examScore.csv'

### 然而直接保存时,会默认保持 index (左边的一列数字),由于后续用 Excel 打开时,本身就带序号,还是从1开始,符合人性,所以很多人不希望输出 index,那要怎么办呢?

df.to_csv(sava_path,index = False)  ### 加个index = False就好了


好吧这篇先说到这里吧,写得够多的了。
最终的目的是将数据整理好,形成 dataframe 的样子
再次强调,这篇文章的目的是将数据整理成下面这个

#----------------------结果
#        name  Exam1  Exam2  Exam3
# 0  xiaoming     97     49     79
# 1     laoli     87     56     38
# 2  zhubajie    100     87     97
#

下一篇,我打算讲 dataframe 的数据处理操作 (筛选,过滤,合并,计算,重塑等)
再下一篇,我想将统计学的内容整理进来
至此,才能优雅的玩转数据


最后说一下,我写的方法其实不是最优雅的,一定有其他计算机大佬写得比我漂亮 (比较我是生物背景的)。所以如果本篇出现错漏,或者有更好地思路,欢迎留言讨论。

后记,原本不想写那么多的,因为写太多可读性差,看一会就不想看了。
但是我个人的使用经验又是各部分都有关联,如果单独拆开来讲就不太实用,”知道是什么“和“知道如何灵活使用”还是两码事。
所以我想综合写一下比较实用的思路,至于每个数据类型的细节处理,以后会独立成章,有空再分享吧。

作者:发哥
链接:发哥的档案室 - 简书
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

推荐阅读更多精彩内容

  • 在python中,众所周知,数据预处理最好用的包就是pandas了,以下是pandas里的dataframe数据结...
    天涯海角醉云游阅读 29,444评论 1 12
  • pyspark.sql module Module context Spark SQL和DataFrames中的重...
    盗梦者_56f2阅读 5,032评论 0 19
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 8,663评论 0 13
  • 夕阳含蓄斜落,江面泛起层层鳞片,一艘渔船顺水而过,岁月犹如滔滔河水,洗去羞涩的青春年华,但洗不掉,曾经的约定。 遥...
    linhaolin阅读 87评论 0 0
  • 入夜,朋友三四的聚餐,酒足饭饱之后,放下手机,谈人生,讲经历。已婚女人们的话题,除了孩子就是老公。你谈你成绩优异...
    梦飞尘阅读 195评论 0 0
  • 2019年计划 2019年的第一天,依旧上计划,我尽量设置少而精的目标,希望能有新的突破 工作 1.教学问题研究 ...
    帅_帅阅读 17评论 0 0