Content

  • 1. Pandas IO中的坑
    • 1.1 解决读的坑,让pandas读文件内存占用减小 80%
    • 1.2 解决写的坑,让磁盘空间节约60%
    • 1.3 解决写的坑,避免挖个坑
    • 1.4 python2:加上encoding, 读写好习惯
    • 1.5 乱入:用pandas进行onehot的神坑
  • 2. DataFrame 链式索引的坑
    • 2.1 解决:SettingWithCopyWarning:
    • 2.2 DataFrame 里存None:这个坑是真的坑
    • 2.3 这个坑不算坑
  • 3. DataFrame 拼接里面的坑与技巧
    • 3.1 concat:坑虽小,须谨慎
    • 3.2 merge:小众的技巧
  • 4. 一些技巧
    • 4.1 pandas 画图
    • 4.2 简单的相关性分析
  • 5. 结束语

在进行数据处理和分析时,pandas就像一条高速公路,能够帮助我们快速的进行各种数据处理和分析操作。但是高速公路也可能有各种坑,一不小心就翻车。

在平时的工作中,也积累了pandas处理的各种坑,记录下来,跟大家分享一下。

文章同步发在我的个人博客,欢迎大佬们指教。Pandas 那些年踩过的坑

import pandas as pd
import numpy as np

1. Pandas IO中的坑

先从pandas的读写操作写起。使用pandas读写CSV文件的最常见的操作,即使这个最简单的操作,就很有可能掉入坑里。

1.1 解决读的坑,让pandas读文件内存占用减小 80%

资源总是有限的, 僧多肉少是常见的

而我一次在公司的机器学习平台申请到5G内存,需要打开的csv文件只有900M,当你信心满满的使用 pandas.read_csv 去读取文件,意想不到的是内存爆了, 内存爆了,内存爆了!!!
于是乎,就去学习了一下pandas在内存中存数据的方式,并且找到了解决方式,并很好的填了这个坑。

一般来说,用pandas处理小于100M的数据,性能不是问题。当用pandas来处理几百兆甚至几个G的数据时,将会比较耗时,同时会导致程序因内存不足而运行失败。那么怎么就解决这个问题呢,我们先来讨论一下pandas的内存使用。

如下表所示,pandas共有6种大的数据类型,在底层pandas会按照数据类型将列分组形成数据块(blocks), 相同数据类型的列会合到一起存储。实际上,对于整型和浮点型数据,pandas将它们以 NumPy ndarray 的形式存储。

从表中可以看到,不同的存储方式所占用的内存不同。其中类型为category的数据在底层使用整型数值来表示该列的值,而不是用原值。当我们把一列转换成category类型时,pandas会用一种最省空间的int子类型去表示这一列中所有的唯一值。当一列只包含有限种值时,这种设计是很不错的。

了解到这里,我们是不是可以将占用内存多的数据类型转为占用内存低的数据类型,以到达减小内存的占用的目的。

memory usage float int unit category bool object
1 bytes int8 unit8
2 bytes float16 int16 unit16
4 bytes float32 int32 unit32
8 bytes float64 int64 unit64
variable Slytherin category bool object

随便找了个数据,实际操作看一下:

data = pd.read_csv('game_logs.csv')
data.head()
/Users/kk_j/anaconda3/envs/python2_for_project/lib/python2.7/site-packages/IPython/core/interactiveshell.py:2717: DtypeWarning: Columns (12,13,14,15,19,20,81,83,85,87,93,94,95,96,97,98,99,100,105,106,108,109,111,112,114,115,117,118,120,121,123,124,126,127,129,130,132,133,135,136,138,139,141,142,144,145,147,148,150,151,153,154,156,157,160) have mixed types. Specify dtype option on import or set low_memory=False.interactivity=interactivity, compiler=compiler, result=result)
date number_of_game day_of_week v_name v_league v_game_number h_name h_league h_game_number v_score ... h_player_7_name h_player_7_def_pos h_player_8_id h_player_8_name h_player_8_def_pos h_player_9_id h_player_9_name h_player_9_def_pos additional_info acquisition_info
0 18710504 0 Thu CL1 na 1 FW1 na 1 0 ... Ed Mincher 7.0 mcdej101 James McDermott 8.0 kellb105 Bill Kelly 9.0 NaN Y
1 18710505 0 Fri BS1 na 1 WS3 na 1 20 ... Asa Brainard 1.0 burrh101 Henry Burroughs 9.0 berth101 Henry Berthrong 8.0 HTBF Y
2 18710506 0 Sat CL1 na 2 RC1 na 1 12 ... Pony Sager 6.0 birdg101 George Bird 7.0 stirg101 Gat Stires 9.0 NaN Y
3 18710508 0 Mon CL1 na 3 CH1 na 1 12 ... Ed Duffy 6.0 pinke101 Ed Pinkham 5.0 zettg101 George Zettlein 1.0 NaN Y
4 18710509 0 Tue BS1 na 2 TRO na 1 9 ... Steve Bellan 5.0 pikel101 Lip Pike 3.0 cravb101 Bill Craver 6.0 HTBF Y

5 rows × 161 columns

data.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 171907 entries, 0 to 171906
Columns: 161 entries, date to acquisition_info
dtypes: float64(77), int64(6), object(78)
memory usage: 738.1 MB

可以看到这个数据占用内存738.1M,而文件原来的大小仅仅128M,内存占用是原文件大小的 6 倍!!!

再来尝试一下在打开文件的时候指定列的类型,将数据类型为object的列变成category的数据类型。

object_cols = data.select_dtypes(include=['object']).columns.tolist()
dtype_list = ['category' for x in object_cols]
cols_dtype_dict = dict(zip(object_cols, dtype_list))
data1 = pd.read_csv('game_logs.csv', dtype=cols_dtype_dict, date_parser=['date'], infer_datetime_format=True)
data1.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 171907 entries, 0 to 171906
Columns: 161 entries, date to acquisition_info
dtypes: category(78), float64(77), int64(6)
memory usage: 157.2 MB

可以看到,内存占用从 738.1M 降到了157.2M,有效降低 78.7%, 而且那一堆Warning 也没了

很开心对不对,没有资源,咱自己创造资源

1.2 解决写的坑,让磁盘空间节约60%

经常听见有小伙伴说,XXXX服务器磁盘空间又满了,大家清理一下自己不用的数据,数据很重要,不能删怎么办。

还是那句话,没有资源,咱创造资源

data1.to_csv('game_logs.gz', compression='gzip', index=False)

去磁盘再去看看文件大小,是不是磁盘变大了。错了,是不是文件变小了。

在我的电脑里,这个文件从 128M 减小到18M。我去,磁盘占用减小了86%

那读取的时候怎么办呢,读取方式不变,还是 read_csv

1.3 解决写的坑,避免挖个坑

这个坑比较简单,但是一不小心就翻车。看个例子

df = pd.DataFrame(np.random.rand(2,2), columns=['a', 'b'])
df
a b
0 0.977292 0.343893
1 0.478050 0.781146
df.to_csv('test_df.csv')
df1 = pd.read_csv('test_df.csv')
df1
Unnamed: 0 a b
0 0 0.977292 0.343893
1 1 0.478050 0.781146

通过以上例子,可以看到,一存一读间,却多了一列。
这种情况极易给后面的操作埋下一个大坑,而且还蒙在鼓里找不出原因。

怎么解决呢,只需要在存的时候,指定 index 参数为 False 即可。再来试一下:

df.to_csv('test_df.csv', index=False)
df = pd.read_csv('test_df.csv')
df
a b
0 0.977292 0.343893
1 0.478050 0.781146

1.4 python2:加上encoding, 读写好习惯

这个就不举例子讲了。但是讲一下原因。

在工作中经常处理带中文字符的csv文件,一个好的习惯是,在使用pandas的read_csv(其他的read操作一样)进行文件读取时,加上参数 encoding=‘utf-8’,并且在数据的操作中都始终使用utf-8的编码格式,会减少非常多的坑。另外在使用 .to_csv 存储带有中文字符的DataFram数据时,加上参数 encoding=‘utf-8-sig’,这样存成的csv就可以用excel打开,而不乱码。

关于编码知识,可以看这里:https://blog.csdn.net/u010223750/article/details/56684096/

1.5 乱入:用pandas进行onehot的神坑

机器学习特征工程中,经常会用到one-hot编码。并且pandas中已经提供了这一函数pandas.get_dummies()。
但是使用这个函数进行one hot操作后得到的数据类型竟然是是uint8,如果进行数值计算时会溢出。

data_df = pd.DataFrame({'sex': ['male', 'female', 'female', 'female', 'female', 'male', 'female'],'height': [182, 160, 176, 172, 174, 170, 155],'weight': [65, 50, 55, 48, 48, 100, 80],'is_air_hostesses': [1, 1, 1, 1, 1, 0, 0]})
data_df
height is_air_hostesses sex weight
0 182 1 male 65
1 160 1 female 50
2 176 1 female 55
3 172 1 female 48
4 174 1 female 48
5 170 0 male 100
6 155 0 female 80
sex_one_hot_df = pd.get_dummies(data_df['sex'])
sex_one_hot_df
female male
0 0 1
1 1 0
2 1 0
3 1 0
4 1 0
5 0 1
6 1 0
sex_one_hot_df.dtypes
female    uint8
male      uint8
dtype: object
-sex_one_hot_df
female male
0 0 255
1 255 0
2 255 0
3 255 0
4 255 0
5 0 255
6 255 0

这真的是一个神坑,如果特征比较多的话,根本发现不了。如果没有发现,后续如果做其他操的时候,就会出错。这个坑藏得深啊。

正确的做法是转换一下数据类型:

sex_one_hot_df = sex_one_hot_df.astype('float')

2. DataFrame 链式索引的坑

2.1 解决:SettingWithCopyWarning:

SettingWithCopyWarning 可能是人们在学习 Pandas 时遇到的最常见的障碍之一。
首先来看看,它出现的情况之一(其他情况大同小异):

sub_df = df.loc[df.a > 0.6]
sub_df
a b
0 0.688818 0.510446
4 0.945565 0.801788
sub_df['c'] = [1,2]
/Users/kk_j/anaconda3/envs/python2_for_project/lib/python2.7/site-packages/ipykernel_launcher.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value insteadSee the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy"""Entry point for launching an IPython kernel.

没有出任何意外,SettingWithCopyWarning 出现。首先要理解的是,SettingWithCopyWarning 是一个警告 Warning,而不是错误 Error,它告诉你,你的操作可能没有按预期运行,需要检查结果以确保没有出错。当你查看结果,发现结果没有错,就是在按预期进行,你极有可能忽略这个Warning, 而当下次它再次出现时,你不会再检查,然后错误就出现了。

直接说他出现的原因,那就是链式索引产生的新的变量并没有在内存中创建副本,当接下来对新的变量进行修改时,有修改原数据的风险。

怎么解决呢。很简单,只需要在链式索引后面加上一个.copy() 即可:

sub_df = df.loc[df.a > 0.6].copy()
sub_df['c'] = [1,2]
sub_df
a b c
0 0.688818 0.510446 1
4 0.945565 0.801788 2

再试试,可以看到没有再出现问题。

但是我们也注意到,在Warning的提示里,提到:Try using .loc[row_indexer,col_indexer] = value instead。这也是一种解决办法,当你仅仅是想更改原始数据,你可以使用这个操作。

对这个问题的详细原理讲解,请参考: https://www.dataquest.io/blog/settingwithcopywarning/

2.2 DataFrame 里存None:这个坑是真的坑

真的不好写开场白,直接上例子:

v = {'value': 'a'}
d = [{'name': 'class', 'age': 10}, {'name': None, 'age': 11}, {'name': 'def', 'age': 9}]
df = pd.DataFrame(d)new_1 = df[(df['age'] >= 10) | df['name'].str.contains(v['value'])]# 颠倒里面条件的顺序
new_2 = df[df['name'].str.contains(v['value']) | (df['age'] >= 10)]print('-'*40)
print(df)print('-'*40)
print(new_1)print('-'*40)
print(new_2)
----------------------------------------age   name
0   10  class
1   11   None
2    9    def
----------------------------------------age   name
0   10  class
1   11   None
----------------------------------------age   name
0   10  class

这。。。。。逻辑操作“或”俩边的条件对调下,结果也能不一样?一脸懵逼。

但是接下来,我进行了简单的探索。

df['age'] >= 10
0     True
1     True
2    False
Name: age, dtype: bool
df['name'].str.contains(v['value'])
0     True
1     None
2    False
Name: name, dtype: object
(df['age'] >= 10) | df['name'].str.contains(v['value'])
0     True
1     True
2    False
dtype: bool
df['name'].str.contains(v['value']) | (df['age'] >= 10)
0     True
1    False
2    False
dtype: bool

这。。。。。。还是一脸懵逼。

百度了一圈,还是没有找到答案。但是找到了解决了办法:
把 None 改为了 ‘’ 就可以了。

v = {'value': 'a'}
d = [{'name': 'class', 'age': 10}, {'name': '', 'age': 11}, {'name': 'def', 'age': 9}]
df = pd.DataFrame(d)new_1 = df[(df['age'] >= 10) | df['name'].str.contains(v['value'])]# 颠倒里面条件的顺序
new_2 = df[df['name'].str.contains(v['value']) | (df['age'] >= 10)]print('-'*40)
print(df)print('-'*40)
print(new_1)print('-'*40)
print(new_2)
----------------------------------------age   name
0   10  class
1   11
2    9    def
----------------------------------------age   name
0   10  class
1   11
----------------------------------------age   name
0   10  class
1   11

2.3 这个坑不算坑

这里就举个例子,自己体会:

print type(df['age'])
df['age']
<class 'pandas.core.series.Series'>0    10
1    11
2     9
Name: age, dtype: int64
print type(df[['age']])
df[['age']]
<class 'pandas.core.frame.DataFrame'>
age
0 10
1 11
2 9

前面是Series后面是DataFrame,这不知道算不算一个坑

3. DataFrame 拼接里面的坑与技巧

pandas 里多个DataFrame的拼接,主要是append, merge,concat,join四个函数。想详细了解的话看一下官方文档。

这里简单说一下concat和merge.

3.1 concat:坑虽小,须谨慎

解释这个坑,也只有靠例子。直接上代码:


df1 = pd.DataFrame({ 'A': ['A0', 'A1', 'A2'],'B': ['B0', 'B1', 'B2'],'C': ['C0', 'C1', 'C2'],'D': ['D0', 'D1', 'D2']})df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6'],'B': ['B4', 'B5', 'B6'],'C': ['C4', 'C5', 'C6'],'D': ['D4', 'D5', 'D6']})df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10'],'B': ['B8', 'B9', 'B10'],'C': ['C8', 'C9', 'C10'],'D': ['D8', 'D9', 'D10']})frames = [df1, df2, df3]
result = pd.concat(frames)
result
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
0 A4 B4 C4 D4
1 A5 B5 C5 D5
2 A6 B6 C6 D6
0 A8 B8 C8 D8
1 A9 B9 C9 D9
2 A10 B10 C10 D10
df4 = pd.DataFrame({'val':[0,1,2,3,4,5,6,7,8],'A': ['A0', 'A1', 'A2', 'A3','A4', 'A5', 'A6', 'A7','A8']})
result['val'] = df4['A']
result
A B C D val
0 A0 B0 C0 D0 A0
1 A1 B1 C1 D1 A1
2 A2 B2 C2 D2 A2
0 A4 B4 C4 D4 A0
1 A5 B5 C5 D5 A1
2 A6 B6 C6 D6 A2
0 A8 B8 C8 D8 A0
1 A9 B9 C9 D9 A1
2 A10 B10 C10 D10 A2

注意看最后一列 ‘val’ ,和我们预期(预期的是从 A0-A8 )的真的不一样。原来赋值操作是按照index赋值的,结果就是这么出乎我们的意料。

其实,concat的时候加上参数 ignore_index=True 就好了:

result = pd.concat(frames, ignore_index=True)
result
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A4 B4 C4 D4
4 A5 B5 C5 D5
5 A6 B6 C6 D6
6 A8 B8 C8 D8
7 A9 B9 C9 D9
8 A10 B10 C10 D10
result['val'] = df4['A']
result
A B C D val
0 A0 B0 C0 D0 A0
1 A1 B1 C1 D1 A1
2 A2 B2 C2 D2 A2
3 A4 B4 C4 D4 A3
4 A5 B5 C5 D5 A4
5 A6 B6 C6 D6 A5
6 A8 B8 C8 D8 A6
7 A9 B9 C9 D9 A7
8 A10 B10 C10 D10 A8

3.2 merge:小众的技巧

panda.merge 这个是pandas最常用的操作之一,具体用法可以看官方文档。这里有个小的tricks, 在做一些统计分析的时候很有用。还是具体看例子吧。

left = pd.DataFrame({'key': ['key1', 'key2', 'key3', 'key4'], 'val_l': [1, 2, 3, 4]})
left
key val_l
0 key1 1
1 key2 2
2 key3 3
3 key4 4
right = pd.DataFrame({'key': ['key3', 'key2', 'key1', 'key6'], 'val_r': [3, 2, 1, 6]})
right
key val_r
0 key3 3
1 key2 2
2 key1 1
3 key6 6
df_merge = pd.merge(left, right, on='key', how='left', indicator=True)
df_merge
key val_l val_r _merge
0 key1 1 1.0 both
1 key2 2 2.0 both
2 key3 3 3.0 both
3 key4 4 NaN left_only

_merge 列不仅可以用来检查是否出现数值错误,还可以进行统计分析,比如:

df_merge['_merge'].value_counts()
both          3
left_only     1
right_only    0
Name: _merge, dtype: int64

4. 一些技巧

技巧总是讲不完,我这里随便再写点。

4.1 pandas 画图

这个举个例子就好了

from matplotlib import pyplot as plt
df_merge.val_l.plot(kind='bar')
plt.show()

data1.plot(kind='scatter', x='v_game_number', y='v_score', alpha=0.1)
plt.show()

plot这个命令底层调用的就是matplotlib。必须事先装好matplotlib,不然会报错。

这里的 2 个例子只是抛砖引玉,真正的功能非常强大,有兴趣的小伙伴可以学习一下

4.2 简单的相关性分析

写到这里,血累了。不想去找数据集,还是用前面自己构造的数据集演示一下这个小技巧:

data_df = pd.DataFrame({'sex': ['male', 'female', 'female', 'female', 'female', 'male', 'female'],'height': [182, 160, 176, 172, 174, 170, 155],'weight': [65, 50, 55, 48, 48, 100, 80],'is_air_hostesses': [1, 1, 1, 1, 1, 0, 0]})
data_df
height is_air_hostesses sex weight
0 182 1 male 65
1 160 1 female 50
2 176 1 female 55
3 172 1 female 48
4 174 1 female 48
5 170 0 male 100
6 155 0 female 80
data_df[['sex', 'is_air_hostesses']].groupby(['sex'], as_index=False).mean().sort_values(by='is_air_hostesses', ascending=False)
sex is_air_hostesses
0 female 0.8
1 male 0.5

可以看到女生做空乘的可能性更大一些

data_df['height_band'] = pd.qcut(data_df['height'], 2)
data_df
height is_air_hostesses sex weight height_band
0 182 1 male 65 (172.0, 182.0]
1 160 1 female 50 (154.999, 172.0]
2 176 1 female 55 (172.0, 182.0]
3 172 1 female 48 (154.999, 172.0]
4 174 1 female 48 (172.0, 182.0]
5 170 0 male 100 (154.999, 172.0]
6 155 0 female 80 (154.999, 172.0]

data_df[['height_band', 'is_air_hostesses']].groupby(['height_band'], as_index=False).mean().sort_values(by='is_air_hostesses', ascending=False)
height_band is_air_hostesses
1 (172.0, 182.0] 1.0
0 (154.999, 172.0] 0.5

这里可以看到身高大于172的是空乘的可能性更大一些

同样的也是为了抛砖引玉,不详细介绍了

5. 结束语

写pandas的这些坑,只是为了更好的提高工作效率,有兴趣的小伙伴可以学一学,相信会很有帮助。

Pandas 那些年踩过的坑相关推荐

  1. mac git使用与配置踩过的坑

    #mac git使用与配置踩过的坑 标题mac配置git ssh密钥 参考链接mac配置git ssh key go get安装失败的解决方法 go get约等于git clone+go instal ...

  2. java项目经理也就那么回事_网易PM | 我们之前在需求评审环节踩过的坑...

    原本觉得需求评审也就那么回事儿,大家应该都差不多这么做的,没啥好说的.不过前不久有一位同学问起来我们是怎么做需求评审的,然后发现有一些团队的做法可能还不大一样,他们也还踩着我们之前踩过的坑,他们还在探 ...

  3. Redis 集群部署及踩过的坑

    本文目标 要在单台机器上搭建Redis集群,方式是通过不同的TCP端口启动多个实例,然后组成集群,同时记录在搭建过程中踩过的坑. 安装准备 centos版本:6.7 redis版本:3.2.3 安装方 ...

  4. AWS Device Farm介绍及Appium踩过的坑

    本文记录了在AWS Device Farm上进行Appium TestNG进行手机应用UI自动化测试的流程及遇到的问题,及具体的解决方法.同时记录了使得测试脚本更稳定的一些代码写法. Device F ...

  5. arcgis python 二次开发_我在部署ArcGIS API for Python时踩到的坑

    ArcGIS API for Python相比于其他ESRI产品,还是很年轻.我在部署时踩到了坑,网上也找不到解决方法,很是煞风景,也很打击学习的积极性. 今天回顾一下,做个总结吧.一方面自己备忘,另 ...

  6. 开发路上踩过的坑要一个个填起来————持续更新······(7月30日)

    欢迎转载,请注明出处! https://gii16.github.io/learnmore/2016/07/29/problem.html 踩过的坑及解决方案记录在此篇博文中! 个人理解,如有偏颇,欢 ...

  7. git服务器安装位置,Linux服务器安装gitlabe-runner,并部署包到指定目录,还有踩的一些坑~~...

    [TOC] 前言:上篇文章讲解了如何安装一个本地runner,然后用本地runner发布本地包到Linux,但这会有一个问题,在本地runner用scp向Linux发送文件,会造成服务器上的文件越来越 ...

  8. logstash导入数据到Elasticsearch踩过的坑详解

    一.前言 这篇主要记录在导入数据时候踩到的坑,这些坑总共花费我小一天的时间,记录一下. 二.正文 1.logstash显示在导入数据,ES也成功新建了索引,但是没数据 最开始遇到的是这个坑,logst ...

  9. 学python就业要看哪些书-编程0基础自学Python,踩完这些坑,我才成功就业!

    " 0基础小白学Python的路程不易,但总有一些思路和方法值得借鉴,知道这些能让我们少走很多弯路. 今天我们就来分享2位前辈的学习经验和方法. 零基础学编程的方法 回忆一下我自己学习计算机 ...

  10. 修改 framework 代码的经验和踩过的坑

    点击打开链接 修改 framework 代码的经验和踩过的坑 1 经验 源码主要目录结构 目录 子目录 子目录 描述 android/frameworks/base core java/com/and ...

最新文章

  1. 2000亿次开放学习后,DeepMind的智能体成精了
  2. 网络流24题之餐巾计划问题
  3. 请举出OSI七层模型在实际应用中的实例
  4. NEW RToax logo
  5. 最近新建了一个米表站
  6. php如何删除数据mysql数据库_php数据库如何删除数据
  7. MSSQL2005的新功能创建数据库快照
  8. 手机连接不上电脑的移动热点?
  9. powerAutomate
  10. CodeForces 1K-1400R-1324D
  11. 网易2017春招笔试——赶去公司
  12. python解决凯撒密码
  13. 国内三大云平台,IoT物联网实例选购指南
  14. decode和encode
  15. i春秋WEB CTF 1
  16. 老年食堂:让社区生活充满“幸福滋味”
  17. 淘宝秒杀助手-小助手可以用在聚划算秒杀,付定金秒杀,百亿补贴秒杀
  18. Python使用镜像下载安装包
  19. ycm-core/YouCompleteMe
  20. python 便利店收银系统多少钱_收银系统一套多少钱?应该怎么选择?

热门文章

  1. IT新人的辛酸反省与总结
  2. vue 会议室日历预约组件
  3. 什么是云主机?有什么用途?
  4. NOIP2017翻车记
  5. MATLAB绘图笔记——画箱形图
  6. footer的设置绝不只是设置footer:使html高、宽自适应和footer始终在网页最末尾
  7. Bootstrap实战---footer处理
  8. 第1章 Spring Boot史前简述
  9. 《回炉重造》——集合(容器)
  10. 关于高性能的MIMO技术的实现方法介绍