数据科学中的的十大编码错误

778 阅读6分钟

翻译:疯狂的技术宅

原文:towardsdatascience.com/top-10-codi…

数据科学家是“比任何软件工程师都更擅长统计数据的人,而且比任何统计学家都更擅长软件工程”。许多数据科学家都有统计学背景,并且在软件工程方面经验很少。我是一名高级数据科学家,在 Stackoverflow 上的 python 板块排名前 1%,并与许多(初级)数据科学家合作。以下是我经常看到的 10 个常见错误列表。

1. 不共享代码中引用的数据

数据科学需要代码和数据。因此为了让其他人能够重现你的结果,他们需要能够访问数据。但很多人忘记了共享他们的代码用到的数据。

import pandas as pd
df1 = pd.read_csv('file-i-dont-have.csv') # fails
do_stuff(df)

解决方案:使用 d6tpipe与 你的代码共享数据文件或上传到 S3、web、google drive 等或保存到数据库,以便他人可以检索文件(但不要不要把它们添加到 git 中,见下文)。

2. 对无法访问的路径做了硬编码

与错误 1 类似,如果你对其他人无权访问的路径进行了硬编码,那么他们无法运行你的代码并且必须检查许多地方以手动更改路径。 Booo!

import pandas as pd
df = pd.read_csv('/path/i-dont/have/data.csv') # fails
do_stuff(df)
# or 
impor os
os.chdir('c:\\Users\\yourname\\desktop\\python') # fails

解决方案:使用相对路径、全局路径配置变量或 d6tpipe 使你的数据易于访问。

3. 将数据与代码混在一起

由于数据科学的代码需要数据,为什么不把它们放在一起呢?不过当你在查看自己项目的时候,会发现数据文件、图片、输出结果和其他垃圾文件混在一起。哎呀,真是一团糟!

├── data.csv
├── ingest.py
├── other-data.csv
├── output.png
├── report.html
└── run.py

解决方案:将你的目录按照类别进行组织,如数据、报告、代码等。请参阅Cookiecutter数据科学d6tflow 项目模板 并使用第一条中提到的工具来存储和共享数据。

4. 用 Git 把数据和源代码一起提交

大多数人现在对他们的代码进行版本控制(如果你不这样做那就是另一个错误!!见git)。在共享数据时,可能很容易将数据文件添加到版本控制中。这对于非常小的文件是可以的,但是 git 没有针对数据进行优化,尤其是大文件。

git add data.csv

解决方案:使用第一条中提到的工具来存储和共享数据。如果你真的想要用版本控制系统去管理数据,请参阅d6tpipeDVCGit 大文件存储

5. 编写函数而不是 DAG

关于数据的问题够多了,接下来让我们谈谈实际的代码!由于你在学习编码时第一个学到的就是函数,因此数据科学代码主要被组织为一系列线性运行的函数。这样会导致一些问题,请参阅机器学习代码出问题的4个原因

def process_data(data, parameter):
    data = do_stuff(data)
    data.to_pickle('data.pkl')
data = pd.read_csv('data.csv')
process_data(data)
df_train = pd.read_pickle(df_train)
model = sklearn.svm.SVC()
model.fit(df_train.iloc[:,:-1], df_train['y'])

**解决方案:**数据科学代码不是对函数进行线性链接,而是更好地写为一组具有依赖关系的任务。使用 d6tflowairflow

6. 过多的依赖循环

与函数一样,for 循环是学习编码时学到的第一件事。这容易理解,但它们很慢而且过于冗长,这只能说明你不知道矢量化,而用循环来进行替代。

x = range(10)
avg = sum(x)/len(x); std = math.sqrt(sum((i-avg)**2 for i in x)/len(x));
zscore = [(i-avg)/std for x]
# should be: scipy.stats.zscore(x)
# or
groupavg = []
for i in df['g'].unique():
	dfg = df[df[g']==i]
	groupavg.append(dfg['g'].mean())
# should be: df.groupby('g').mean()

解决方案Numpyscipypandas 可以帮你把很多你认为可能需要循环的工作矢量化。

7. 不写单元测试

随着数据、参数或用户输入的变化,你的代码可能会发生异常中断,你可能不会注意这点,但是这可能导致输出错误,如果有人根据你的输出做出决定,那么糟糕的数据将会导致错误的决策!

**解决方案:**使用 assert 语句检查数据质量。 pandas 有类似的测试,也可以用 d6tstack 检查数据提取和 d6tjoin 对数据连接进行。代码示例数据检查:

assert df['id'].unique().shape[0] == len(ids) # have data for all ids?
assert df.isna().sum()<0.9 # catch missing values
assert df.groupby(['g','date']).size().max() ==1 # no duplicate values/date?
assert d6tjoin.utils.PreJoin([df1,df2],['id','date']).is_all_matched() # all ids matched?

8. 不给代码加注释

我明白了,你急着做一些分析。你把事情搞混在一起,把结果交给你的客户或老板。然后一个星期后,他们回来说“你能改变xyz”或“你能更新吗”。你看看你的代码,不记得你为什么做了你做的。现在想象其他人必须运行它。

def some_complicated_function(data):
	data = data[data['column']!='wrong']
	data = data.groupby('date').apply(lambda x: complicated_stuff(x))
	data = data[data['value']<0.9]
	return data

解决方案:即使在你提供数据分析的代码之后,也要花费额外的时间来对你的所作所为进行注释。你会感谢自己,同样别人也会这样做!这使你看起来更像专业人士!

9. 将数据保存为 csv 或 pickle 格式

再回到数据,毕竟这是数据科学。就像函数和 for 循环一样,CSV 和 pickle 文件格式也是常用的,但实际上它们并不是很好。 CSV 不包含结构,因此别人必须再次解析数字和日期。尽管 Pickles 解决了这个问题,但只能在 python 中使用而且不会被压缩。两者都不是存储大型数据集的好格式。

def process_data(data, parameter):
    data = do_stuff(data)
    data.to_pickle('data.pkl')
data = pd.read_csv('data.csv')
process_data(data)
df_train = pd.read_pickle(df_train)

解决方案:使用 parquet 格式或其他二进制数据格式与数据模式,在合适的情况下压缩数据。 d6tflow 能自动将任务的数据输出保存为 parquet 格式,因此你无需另行处理。

10. 使用 jupyter notebook

这是一个有争议的结论:jupyter 笔记本和 CSV 一样普遍,很多人都在用它,这并不意味着它是合适的。 Jupyter notebook 造成了上面提到的许多不良软件工程的习惯,特别是:

  1. 你很想将所有文件保存到同一个目录中
  2. 编写运行于上下文代码关系而不是 DAG 的代码
  3. 你没有把你的代码模块化
  4. 难以调试
  5. 代码和输出混在同一个文件中
  6. 没有良好的版本控制

Solution: Use pycharm and/or spyder.

解决方案:使用 pycharmspyder