0
想要让你的代码更专业,最好的方法就是使其可重用。
「可重用」是什么意思?在你的数据科学职业生涯中的某个时刻,你编写的代码将被使用不止一次或两次。也许你会对一些不同的图像文件集运行相同的预处理管道,或者你有一套用于比较模型的评估技术。我们都复制并粘贴了相同的代码,但是一旦你发现自己复制了相同的代码不止一次或两次,那就应该花点时间使你的代码可重用。重用好的代码并不是欺骗或懈怠:它是对时间的有效利用,并且被认为是软件工程中的最佳实践。
我认为有六个核心原则:1)让你或你的同事很容易重用你的代码;2)让你的代码看起来非常精良和专业;最重要的是,3)节省你的时间。
模块化:代码被分解成独立的小部分(如函数),每个部分都做一件事。
正确:你的代码做的是你想要的事情。
可读性:很容易阅读代码并理解它的作用。变量名是信息性的,代码有最新的注释。
风格:代码遵循单一、一致的风格(例如,r 的 tidyverse 风格指南,python 代码的 pep 8)
通用性:解决一个不止一次发生的问题,并预测数据的变化。
创造性:解决一个尚未解决的问题,或者是对现有解决方案的明显改进。
让我们更详细地介绍一下这些步骤中的每一步,并给出一些示例代码,看看它们在实践中是如何工作的。
模块化
模块化代码意味着你的代码被分解成独立的小部分(比如函数),每个部分都做一件事。
无论是在 python 还是 r 中,每个函数都有以下几个部分:
函数的名称。
函数的参数。这是你将传递到函数中的信息。
函数体。这是定义函数功能的地方。通常,我会为我的函数编写代码,并首先使用现有的数据结构进行测试,然后将代码放入函数中。
返回值。这是你的函数在完成编写后将返回的内容。在 python 中,需要通过在函数底部添加 return(thing_to_return)来指定要返回的内容。在 r 中,默认情况下将返回函数体最后一行的输出。
让我们看一些例子。这里有两个示例函数,一个在 python 中,一个在 r 中,它们做了相同的事情(或多或少)。
它们都有相同的函数名,find_most_common
他们都有一个参数,values
它们都有一个执行大致相同操作的主体:计算值中每个 values 显示的次数
它们都返回相同的内容:输入参数值中最常见的值
python 示例
# define a function
def find_most_common(values):
list_counts = collections.Counter(values)
most_common_values = list_counts.most_common(1)
return(most_common_values[0][0])
# use the function
find_most_common([1, 2, 2, 3])
r 示例
# define the function
find_most_common <- function(values){
freq_table <- table(values)
most_common_values <- freq_table[which.max(freq_table)]
names(most_common_values)
}
# use the function
find_most _common(c(1, 2, 2, 3))
很直截了当,对吧?(即使两种语言之间的语法有点不同)。你可以使用这个编写小函数的一般原则,每个函数只做一件事,将代码分解成更小的片段。
为什么是函数?
如果你有更多的编程经验,你可能会好奇为什么我选择谈论函数,而不是类或其他相关概念从[面向对象编程]。我认为函数式编程适合于很多数据科学工作,所以这是我将用来向你展示模块化代码示例的一般框架。
函数式编程。一种编写代码的方式,在这种方式下,你将一个或多个数据片段传递到一个函数中,然后返回的结果将是这些数据片段的某种转换。这意味着你不需要修改函数体中的现有变量。如果你有兴趣了解更多,我建议你看下这篇讨论。
我喜欢将函数方法用于数据科学的主要原因是,它使将多个函数链接到一个数据处理管道变得容易:一个函数的输出成为下一个函数的输入。就像这样:
数据->函数 1->函数 2->函数 3->转换数据
有一些非常有用的工具可以帮助你做到这一点,包括 r 中的 pipes 和 python 中 pyjanitor 的方法接。
python 示例:将函数链接在一起
本例基于 pyjanitor 文档中的一个示例,向你展示了如何使用现有 pandas 函数设置一个小的数据管道。
它读取一个文件(pd.read_excel('dirty_data.xlsx')行),然后使用一些函数对其进行转换,这些函数可以清除列名、删除丢失的数据、重命名其中一列并将其中一列转换为 datetime 格式。输出也是一个数据帧。
cleaned_df = (
pd.read_excel('dirty_data.xlsx')
.clean_names()
.remove_empty()
.rename_column("full_time_", "full_time")
.convert_excel_date("hire_date")
)
cleaned_df
r 示例:将函数链接在一起
这里有一个 r 示例,它执行与 python 示例相同的操作。
cleaned_df <- read_excel('dirty_data.xlsx') %>%
clean_names() %>%
remove_empty() %>%
renames(“full_time”, “full_time_”) %>%
excel_numeric_to_date(“hire_date”)
将代码分解为函数(特别是如果每个函数只转换传递给它的数据)可以让你重用代码并将不同的函数组合成紧凑的数据管道,从而节省时间。
正确
我所说的「正确」是指你的代码按照你说的/认为的那样去做。这很难检查。确保代码正确的一种方法是代码审查。
代码审查是一个过程,在这个过程中,你的同事仔细检查你的代码,以确保它的工作方式和你认为的是一样的。
不幸的是,这对数据科学家来说并不总是可行的。尤其是如果当你是公司里唯一的数据科学家时,很难让一个没有统计学或机器学习背景的人给你提供关于你的代码的可靠反馈。随着这个领域的发展,数据科学代码进行代码审查可能变得更加常见……但同时,你可以通过一些测试来帮助审查代码是否正确。
测试是使用一小段代码检查你的代码是否正常工作。
测试用例不必写得很复杂!在这里,我将研究如何用一行代码向函数添加测试。
在上面编写的 python 函数中,我返回了最常见的值……但是如果有多个返回值的情况怎么办?
assert 是一个内置在 python 中的方法,它帮助我们检查某些内容是否正确。如果是正确的,那么什么都不会发生。否则,我们的函数将停止运行并给出报错信息。
import collectionsdef find_most_common(values):
""""Return the value of the most common item in a list"""
list_counts = collections.Counter(values)
top_two_values = list_counts.most_common(2)
# make sure we don't have a tie for most common
assert top_two_values[0][1] != top_two_values[1][1]\ ,"There's a tie for most common value"
return(top_two_values[0][0])
这里的 assert 语句检查最常用值的计数是否与第二个最常用值的计数不同。如果是,函数将停止并返回错误消息。
首先,让我们检查一下,如果没有 tie,我们的功能是否会按预期工作:
到目前为止还不错:5 比任何其他值都多。但如果有 tie 呢?
我们得到一个 assertion 错误和一个很有用的错误信息!
虽然这是一个非常简单的例子,但是包含一些测试可以帮助你确保代码正在做你认为它正在做的事情。如果要导入其他库并定期更新它们,这一点尤其重要:不要仅仅因为你没有更改代码,就认为要导入的代码没有更改!测试可以帮助你在错误引起问题之前找到它们。
使用测试检查代码是否正确可以帮助快速捕获错误,从而节省时间。
可读性
「可读」代码是易于阅读和理解的代码,即使这是你第一次看到这段代码。一般来说,变量名和函数名之类的单词表示的实际的意思,则代码越容易阅读。此外,描述代码在做什么或为什么做出特定选择的注释可以帮助提高代码的可读性。
变量名
变量名是信息性的,代码有最新的注释和 docstring。
一些不太可读的变量名示例如下:
单个字符,如 x 或 q。有一些例外,如使用 i 作为索引或 x 作为 x 轴。
所有小写名称之间没有空格,例如 likethisexample 或者 somedatafromsomewhere。
非格式化或不明确的名称,例如 data2 不会告诉你数据中的内容或者它与 data1 的区别。df 告诉你某个东西是一个数据帧……但是如果你有多个数据帧,你怎么知道它是哪一个?
你可以通过以下几个规则改进命名:
使用某种方式指示变量名中单词之间的空格。由于你不能使用实际的空格,常用的方法是 snake_case 和 camelcase。
使用这些名称来描述变量中的内容或函数的作用。例如,sales_jan 的信息量比单纯的 data 要大,z_score_calculator 的信息量比单纯的 calc 或 norm 的要大。
当你还在想怎么写代码的时候,我建议你返回去,把变量名取得更好。
注释
注释是代码中解释的文本。在 python 和 r 中,可以通过以 # 开头来表示该行是注释。更好地写注释的一些技巧:
虽然有些风格指南建议不要包含关于代码在做什么的信息,但我实际上认为这在数据科学中是有道理的。
如果你更改了代码,请记住更新注释!
如果你用做某件事的方式非同寻常,那么也需要加上一条注释来解释为什么这样做,如果接下来有人尝试更新代码,会不会遇到问题。
有些风格指南只建议你用英语写注释,但是如果你和使用另一种语言的人一起工作,我个人建议你用同事最容易理解的语言来写注释。
docstring:在 python 中,docstring 是函数或类中第一位文本的注释。如果要导入函数,则应包含 docstring。这使你和其他使用该函数的人能够快速了解该函数的功能。
def function(argument):
“”” This is the docstring. It should describe what the function will do when run”””
若要检查函数的 docstring,可以使用语法函数「function_name.__doc__」。
如果您是 r 用户,并且要向代码中添加 docstring,则可以使用 docstring 包。
具有可读性的代码读起来更快。当你需要回到一个项目,或者当你第一次遇到新的代码并且需要了解正在发生的事情时,这会节省你的时间。
风格
当我在这里说「风格」时,我的字面意思是「遵循特定的风格」。风格在称为「风格指南」的文档中进行描述和定义。如果你以前没用过风格指南,那就很方便了!遵循特定的风格指南可以使代码更易于阅读,并帮助你避免常见错误。
风格指南将提供一些指导,比如在哪里需要空格、如何组织文件中的代码结构以及如何命名函数和文件等。不遵循风格指南的代码可能仍然运行得很好,但是看起来有点奇怪,而且通常很难阅读。
pro tip:实际上,你可以使用一个名为「linter」的程序来自动检查代码是否遵循特定的样式指南。python 的 pylint 和 r 的 lintr 是两个流行的 linter。你可以在这里看到如何使用 linter 检查 r 实用程序脚本的示例:https://www.kaggle.com/rtatman/linting-scripts-in-r 。
一旦你选择了要遵循的风格指南,就应该尽最大努力在代码中始终如一地遵循它。当然,风格指南之间存在差异,但是 python 和 r 风格指南之间的是有共同点的。举几个例子:
你应该将所有导入(库(包)或导入模块名)放在代码文件的顶部,并且每行只有一个导入。
使用制表符缩进或空格缩进取决于你的风格指南,但不应混合使用制表符和空格(例如,有些行用两个空格缩进,有些行用制表符缩进)。
避免在行的末端有空格。
函数和变量的名称都应该用下划线隔开。
尽量使代码行不超过一定的长度,最好少于 80 个字符。
一开始,风格指南可能有点让人难以接受,最好不要太过强调。随着你读写更多的代码,遵循特定的风格指南将变得越来越容易。同时,即使是一些小的改进也会使代码更易于遵循和使用。
例子
对于这个例子,我们将使用一些 r 代码并修改它以适应 tidyverse 样式指南。
CZS <- function(x) {
sd <- sd(x); m = mean(x)
(x -m)/sd}
我们可以在这里做很多事情,以便遵循 tidyverse 风格指南。
函数名不提供任何信息,也不遵循 tidyverse 约定(小写字母加下划线)。
使用多个赋值运算符(<-和=)。
我们使用的是 tab 和空格。
连接多个行(这是可能的,但在 python 和 r 中都强烈反对)。
我们的中缀运算符周围没有空格(例如+、-、\,等数学符号)。
在新行上没有右大括号 }。
一旦我们处理好这些问题,我们的代码现在看起来是这样的:
calculate_z_score <- function(x) {
sd <- sd(x)
m <- mean(x)
(x - m) / sd
}
我个人认为这比第一个例子更容易阅读,尽管他们做了完全相同的事情。
遵循风格的代码通常更容易阅读和发现错误。此外,大多数风格指南都会推荐最佳实践,以帮助你避免常见的错误。所有这些都节省了你和同事调试的时间。
通用性
「通用性」的意思是在各种情况下都能够使用。具有通用性的代码解决了会不止一次发生的问题,并预期数据变化。
如果我打算重用代码,我应该重写代码吗?
不,当然不是。写新代码来解决一个独特的问题没有错。可能你需要快速重命名一批文件,或者有人要求你为一次性演示制作一个新的、独特的可视化效果。
然而,你可能不想费尽周折,使自己编写的每一行代码都完全可重用。虽然有些人会不同意我的观点,但我个人认为,如果你(或其他人)真的要重用代码,那么只需要花费大量时间来完善代码。
数据科学家必须做很多不同的事情,知道很多不同的事情:比起仔细地润色每一行你曾经编写的代码,你的时间可能能够用在更好的地方。当你知道代码将被重用时,花时间去完善你的代码是有意义的。花一点时间让每件事都更容易理解和使用,可以节省很多时间。
预测数据的变化
我所说的「数据的变化」是指数据中的差异,这些差异会把事情分解开来。例如,你可能编写了一个函数,假设你的数据帧有一个名为 latitude 的列。如果有人下周在数据库中将列的名称更改为 lat,则你的代码运行可能会中断。
为了确保你可以获取预期要获取的数据,可以使用数据验证。我这里有一个 notebook,如果你好奇的话,可以查看关于数据验证更详细地介绍。下面是一些我最喜欢的工具:
python:
我喜欢 csvvalidator 包,以前写过一个介绍:https://www.kaggle.com/rtatman/dashboarding-with-notebooks-day-5 。
对于 python 中的 json 数据,cerberus 模块可能是最流行的工具。
对于可视化丢失的数据,missingno 包非常方便。
要检查文件的类型,python magic 模块可能会有所帮助。
r:
对于 r,用于数据验证的 validate 包(我以前为其编写过教程:https://www.kaggle.com/rtatman/dashboarding-with-notebooks-day-5-r )可能是你的最佳选择。它可以处理表格、层次结构和原始文本数据,这很好。)
要确定文件类型,可以使用 mime 包。
通用代码可以在各种情况下使用。这节省了你的时间,因为你可以在多个不同的地方应用相同的代码。
创造性
我所说的「创造性」是指解决一个尚未解决的问题或是对现有解决方案明显改进的代码。我之所以把这个也包括进来,是为了鼓励你查找现有的库或模块(或 kaggle 脚本)来解决你的问题。如果有人已经编写了你所需的代码,并且该代码允许你使用,那么你可能应该这样做。
如果有明显的改进的话,我建议你写一个库来复制另一个库的功能。例如,python 库 flastext。它允许你做使用正则表达式能做的相同操作(如查找、提取和替换文本),但速度要快得多。
只有在没有现有解决方案的情况下花时间编写代码才能节省时间,因为你可以在现有工作的基础上构建,而不是从头开始。
via:https://www.kaggle.com/rtatman/six-steps-to-more-professional-data-science-code
雷锋网雷锋网雷锋网
雷峰网版权文章,未经授权禁止转载。详情见转载须知。