机器学习中的特征数据选择三部曲

这里我们将对特征工程中经常用到的特征数据选择方法进行介绍使用和说明,主要分为三个部分,分别为:单变量选择、线性模型选择和随机森林模型特征选择。

三部曲一:单变量选择

对于数据科学家或机器学习从业人员而言,对特征选择/排序有很好的了解可能是一笔宝贵的财富。对这些方法的良好掌握可以带来更好的性能模型,可以更好地理解数据的底层结构和特征,并可以更好地了解构成许多机器学习模型基础的算法。使用特征选择通常有两个原因:

1.减少特征数量,以减少过度拟合并提高模型的泛化性。

2.更好地了解功能及其与响应变量的关系。

这两个目标常常彼此矛盾,因此需要不同的方法:根据手头的数据,对目标(1)有利的特征选择方法不一定对目标(2)有利,反之亦然。但是,似乎经常发生的事情是人们不加选择地使用自己喜欢的方法(或从选择的工具中最方便地使用的方法),尤其是更适合于(1)实现(2)的方法。

同时,机器学习或数据挖掘教科书中并未特别详尽地介绍功能选择,部分原因是它们通常被视为学习算法的自然副作用,不需要单独介绍。

在这篇博客文章中,我将尝试介绍一些比较流行的功能选择方法及其优缺点,以及Python和scikit-learn中的代码示例。我还将在样本数据集中并排运行这些方法,这将突出显示它们之间的一些主要差异。在所有示例中,我都专注于回归数据集,但是大多数讨论和示例同样适用于分类数据集和方法。

单变量特征选择

单变量特征选择会单独检查每个特征,以确定特征与响应变量之间关系的强度。这些方法易于运行和理解,通常特别有利于更好地理解数据(但不一定要优化功能集以获得更好的通用性)。单变量选择有很多不同的选项。

皮尔逊相关

理解要素与响应变量之间关系的最简单方法之一是皮尔森相关系数,它可以测量两个变量之间的线性相关性。结果值位于[-1; 1],其中-1表示完全负相关(随着一个变量的增加,另一个变量减小),+ 1表示完全正相关,而0表示两个变量之间没有线性相关。

它计算起来既快速又容易,并且通常是对数据运行的第一件事。Scipy的pearsonr方法同时计算相关性和相关性的p值,粗略显示了不相关系统创建此大小相关性值的可能性。

import numpy as np
from scipy.stats import pearsonr
np.random.seed(0)
size = 300
x = np.random.normal(0, 1, size)
print "Lower noise", pearsonr(x, x + np.random.normal(0, 1, size))
print "Higher noise", pearsonr(x, x + np.random.normal(0, 10, size))

结果如下:

Lower noise (0.71824836862138386, 7.3240173129992273e-49)
Higher noise (0.057964292079338148, 0.31700993885324746)

从示例中可以看到,我们将变量与自身的嘈杂版本进行比较。噪声量较小时,相关性相对较强,p值非常低,而在嘈杂的比较中,相关性非常小,而且p值很高,这意味着很可能会观察到这种相关性 完全是偶然的。

Scikit-learn提供了f_regrssion方法,用于批量计算一组要素的p值(和基础F值),因此这是一种便捷的方法,可以对数据集进行一次关联测试,例如将其包含在 sklearn的管道。

皮尔逊相关作为特征排名机制的一个明显缺点是,它仅对线性关系敏感。如果该关系是非线性的,则即使两个变量之间存在1-1对应关系,Pearson相关也可以接近于零。

例如,当x以0为中心时,x和x2之间的相关性为零。

x = np.random.uniform(-1, 1, 100000)
print pearsonr(x, x**2)[0]

结果如下所示:

-0.00230804707612

有关与上述类似的更多示例,请参见以下示例图。

此外,正如Anscombe的四重奏所说明的那样,仅依靠相关值来解释两个变量的关系可能会产生极大的误导,因此始终值得对数据进行绘图。

互信息和最大信息系数(MIC)

相关估计的另一个更可靠的选择是互信息,定义为:

它测量变量之间的相互依赖性,通常以位为单位。

但是出于两个原因,直接将其用于特征排名可能很不方便。首先,它不是指标,也没有规范化(即不在固定范围内),因此MI值在两个数据集之间是无法比拟的。其次,连续变量的计算可能很不方便:通常,变量需要通过分箱进行离散化,但是互信息得分对分箱选择可能非常敏感。

最大信息系数是为解决这些缺点而开发的技术。它搜索最佳分箱,并将相互信息得分转换为范围[0; 1]的度量。在python中,MIC可在minepy库中获得。

回顾y = x^2的示例,MIC发现互信息为1,即最大。

from minepy import MINE
m = MINE()
x = np.random.uniform(-1, 1, 10000)
m.compute_score(x, x**2)
print m.mic()

结果如下所示:

1.0

人们对MIC的统计能力提出了一些批评,即当原假设为假时,可以拒绝原假设的能力。根据特定的数据集及其嘈杂程度,这可能是问题,也可能不是问题。

距离相关

相关估计的另一种强大的竞争方法是距离相关,它专门设计用于解决Pearson相关的缺点。虽然对于Pearson相关,相关值0并不表示独立性(从x vs x2示例中可以看出),而距离相关性0则表示这两个变量之间没有相关性。

距离相关性例如在R的能量包中提供(还有Python的要点)。

#R-code
> x = runif (1000, -1, 1)
> dcor(x, x**2)
[1] 0.4943864

当关系接近线性时,相对于诸如MIC或距离相关之类的更复杂的方法,偏爱Pearson相关至少有两个原因。一方面,计算Pearson相关性要快得多,这对于大数据集而言可能很重要。其次,相关系数的范围是[-1; 1](而不是MIC和距离相关的[0; 1])。这可以传递关于该关系是负还是正的有用的额外信息,即较高的特征值是否暗示响应变量的较高值,反之亦然。但是,当然只有两个变量之间的关系是单调的时,负相关与正相关的问题才是恰当的。

基于模型的排名

最后,可以使用任意机器学习方法,使用每个单独的功能为响应变量建立预测模型,并测量每个模型的性能。实际上,这已经与Pearson的相关系数一起使用,因为它等效于用于线性回归预测的标准化回归系数。如果特征和响应变量之间的关系是非线性的,则有许多替代方法,例如,基于树的方法(决策树,随机森林),具有基本展开的线性模型等。基于树的方法可能是最简单的方法之一可以应用,因为它们可以很好地对非线性关系建模,并且不需要太多调整。要避免的主要事情是过拟合,因此树的深度应该相对较小,并且应该应用交叉验证。

这是在波士顿住房价格数据集上使用sklearn的随机森林回归器进行的单变量选择,该样本包含了波士顿郊区的住房价格以及许多关键属性。

from sklearn.cross_validation import cross_val_score, ShuffleSplit
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
 
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
 
rf = RandomForestRegressor(n_estimators=20, max_depth=4)
scores = []
for i in range(X.shape[1]):
     score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",
                              cv=ShuffleSplit(len(X), 3, .3))
     scores.append((round(np.mean(score), 3), names[i]))
print sorted(scores, reverse=True)

结果如下所示:

[(0.636, 'LSTAT'), (0.59, 'RM'), (0.472, 'NOX'), (0.369, 'INDUS'), (0.311, 'PTRATIO'), (0.24, 'TAX'), (0.24, 'CRIM'), (0.185, 'RAD'), (0.16, 'ZN'), (0.087, 'B'), (0.062, 'DIS'), (0.036, 'CHAS'), (0.027, 'AGE')]

三部曲二:线性模型和正则化

在我以前的文章中,我讨论了单变量特征选择,其中相对于响应变量独立评估每个特征。另一种流行的方法是利用机器学习模型进行特征排名。许多机器学习模型具有某些固有的内部特征等级,或者很容易从模型的结构生成等级。这适用于回归模型,SVM,决策树,随机森林等。

在这篇文章中,我将讨论使用回归模型的系数来选择和解释特征。这是基于以下思想:当所有特征均处于相同比例时,最重要的特征应在模型中具有最高系数,而与输出变量不相关的特征应具有接近零的系数值。当数据不太嘈杂(或者与要素数量相比有很多数据)并且要素(相对)独立时,即使使用简单的线性回归模型,该方法也可以很好地工作:

from sklearn.linear_model import LinearRegression
import numpy as np
 
np.random.seed(0)
size = 5000
 
#A dataset with 3 features
X = np.random.normal(0, 1, (size, 3))
#Y = X0 + 2*X1 + noise
Y = X[:,0] + 2*X[:,1] + np.random.normal(0, 2, size)
lr = LinearRegression()
lr.fit(X, Y)
 
#A helper method for pretty-printing linear models
def pretty_print_linear(coefs, names = None, sort = False):
    if names == None:
        names = ["X%s" % x for x in range(len(coefs))]
    lst = zip(coefs, names)
    if sort:
        lst = sorted(lst,  key = lambda x:-np.abs(x[0]))
    return " + ".join("%s * %s" % (round(coef, 3), name)
                                   for coef, name in lst)
 
print "Linear model:", pretty_print_linear(lr.coef_)

结果如下所示:

Linear model: 0.984 * X0 + 1.995 * X1 + -0.041 * X2

正如您在本示例中看到的那样,尽管数据中存在很大的噪声,但是该模型确实确实很好地恢复了数据的基础结构。但是实际上,该学习问题特别适合于线性模型:特征与响应变量之间的纯线性关系,而特征之间没有相关性。

当存在多个(线性)相关特征时(就像许多现实生活中的数据集一样),模型变得不稳定,这意味着数据中的小变化会导致模型中的大变化(即系数值),从而进行模型解释非常困难(所谓的多重共线性问题)。例如,假设我们有一个数据集,其中数据的“真实”模型为Y = X1 + X2,而我们观察到Y ^ = X1 + X2 + ϵ,其中ϵ为噪声。此外,假定X1和X2线性相关,使得X1≈X2。理想情况下,学习模型将为Y = X1 + X2。但是根据噪声量ϵ,手头的数据量以及X1和X2之间的相关性,也可能是Y = 2X1(即仅使用X1作为预测变量)或Y = -X1 + 3X2(系数可能恰好适合嘈杂的训练集)等。

让我们看一下与随机森林示例相同的相关数据集,其中添加了一些噪声。

from sklearn.linear_model import LinearRegression
 
size = 100
np.random.seed(seed=5)
 
X_seed = np.random.normal(0, 1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X3 = X_seed + np.random.normal(0, .1, size)
  
Y = X1 + X2 + X3 + np.random.normal(0,1, size)
X = np.array([X1, X2, X3]).T
  
lr = LinearRegression()
lr.fit(X,Y)
print "Linear model:", pretty_print_linear(lr.coef_)

结果如下所示:

Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2

系数总计约3,因此我们可以预期学习的模型表现良好。另一方面,如果我们要解释面值的系数,则根据模型X3对输出变量具有很强的正影响,而X1具有负的影响。实际上,所有功能几乎均与输出变量相关。

相同的方法和注意事项适用于其他类似的线性方法,例如逻辑回归。

正则化模型

正则化是一种用于向模型添加其他约束或惩罚的方法,目的是防止过度拟合并提高泛化性。代替最小化损失函数E(X,Y),最小化的损失函数变为E(X,Y)+α∥w∥,其中w是模型系数的向量,∥⋅∥通常是L1或L2范数, α是可调自由参数,指定正则化量(因此α= 0表示未正则化模型)。对于回归模型,两种广泛使用的正则化方法是L1和L2正则化,在线性回归中应用时也称为套索和岭回归。

L1正则化/套索

L1正则化加罚分α∑ni = 1 | wi |到损失函数(L1-范数)。由于每个非零系数都会增加惩罚,因此它会迫使弱特征的系数为零。因此,L1正则化产生稀疏解,从而固有地执行特征选择。对于回归,Scikit-learn提供Lasso用于线性回归和Logistic回归,L1罚分用于分类。

让我们在具有良好α值的Boston住房数据集上运行Lasso(例如,可以通过网格搜索找到Lasso):

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_boston
  
boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]
  
lasso = Lasso(alpha=.3)
lasso.fit(X, Y)
  
print "Lasso model: ", pretty_print_linear(lasso.coef_, names, sort = True)

结果如下所示:

Lasso model: -3.707 * LSTAT + 2.992 * RM + -1.757 * PTRATIO + -1.081 * DIS + -0.7 * NOX + 0.631 * B + 0.54 * CHAS + -0.236 * CRIM + 0.081 * ZN + -0.0 * INDUS + -0.0 * AGE + 0.0 * RAD + -0.0 * TAX

我们看到许多特征的系数为0。如果进一步增加α,则解将为稀疏和稀疏,即,越来越多的特征的系数为0。

但是请注意,L1正则回归与非正则线性模型相似,因此不稳定,这意味着即使在数据中存在相关特征时,即使在较小的数据变化下,系数(以及特征等级)也可能发生显着变化。这使我们进入了L2正则化。

L2正则化/ Ridge回归

L2正则化(称为线性回归的岭回归)将L2范数罚分(α∑ni = 1w2i)添加到损失函数中。由于系数在惩罚表达式中是平方的,因此它具有与L1范数不同的效果,即,它迫使系数值更均匀地分布。对于相关特征,这意味着它们倾向于获得相似的系数。回到Y = X1 + X2的示例,其中X1和X2高度相关,那么对于L1,无论学习模型是Y = 1 * X1 + 1 * X2还是Y = 2 * X1 + 0,惩罚都是相同的* X2。在这两种情况下,惩罚均为2 *α。但是,对于L2,第一个模型的惩罚为12 + 12 =2α,而对于第二个模型,惩罚为22 + 02 =4α。

这样的结果是模型更加稳定(系数不随较小的数据变化而波动,就像非正规模型或L1模型一样)。因此,尽管L2正则化执行的功能选择与L1不同,但对于特征解释则更有用:预测特征将获得非零系数,而L1通常不是如此。

让我们再次查看具有三个相关特征的示例,使用不同的随机种子运行示例10次,以强调L2回归的稳定性。

from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
size = 100
 
#We run the method 10 times with different random seeds
for i in range(10):
    print "Random seed %s" % i
    np.random.seed(seed=i)
    X_seed = np.random.normal(0, 1, size)
    X1 = X_seed + np.random.normal(0, .1, size)
    X2 = X_seed + np.random.normal(0, .1, size)
    X3 = X_seed + np.random.normal(0, .1, size)
    Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
    X = np.array([X1, X2, X3]).T
 
 
    lr = LinearRegression()
    lr.fit(X,Y)
    print "Linear model:", pretty_print_linear(lr.coef_)
 
    ridge = Ridge(alpha=10)
    ridge.fit(X,Y)
    print "Ridge model:", pretty_print_linear(ridge.coef_)
    print

结果如下所示:

Random seed 0
Linear model: 0.728 * X0 + 2.309 * X1 + -0.082 * X2
Ridge model: 0.938 * X0 + 1.059 * X1 + 0.877 * X2
 
Random seed 1
Linear model: 1.152 * X0 + 2.366 * X1 + -0.599 * X2
Ridge model: 0.984 * X0 + 1.068 * X1 + 0.759 * X2
 
Random seed 2
Linear model: 0.697 * X0 + 0.322 * X1 + 2.086 * X2
Ridge model: 0.972 * X0 + 0.943 * X1 + 1.085 * X2
 
Random seed 3
Linear model: 0.287 * X0 + 1.254 * X1 + 1.491 * X2
Ridge model: 0.919 * X0 + 1.005 * X1 + 1.033 * X2
 
Random seed 4
Linear model: 0.187 * X0 + 0.772 * X1 + 2.189 * X2
Ridge model: 0.964 * X0 + 0.982 * X1 + 1.098 * X2
 
Random seed 5
Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2
Ridge model: 0.758 * X0 + 1.011 * X1 + 1.139 * X2
 
Random seed 6
Linear model: 1.199 * X0 + -0.031 * X1 + 1.915 * X2
Ridge model: 1.016 * X0 + 0.89 * X1 + 1.091 * X2
 
Random seed 7
Linear model: 1.474 * X0 + 1.762 * X1 + -0.151 * X2
Ridge model: 1.018 * X0 + 1.039 * X1 + 0.901 * X2
 
Random seed 8
Linear model: 0.084 * X0 + 1.88 * X1 + 1.107 * X2
Ridge model: 0.907 * X0 + 1.071 * X1 + 1.008 * X2
 
Random seed 9
Linear model: 0.714 * X0 + 0.776 * X1 + 1.364 * X2
Ridge model: 0.896 * X0 + 0.903 * X1 + 0.98 * X2

从示例中可以看到,根据生成的数据,线性回归的系数差异很大。但是,对于L2正则化模型,系数非常稳定,并且紧密反映了数据的生成方式(所有系数均接近1)。

三部曲三:随机森林模型选择

随机森林特征重要性

随机森林由于其相对较高的准确性,鲁棒性和易用性而成为最受欢迎的机器学习方法之一。它们还提供了两种简单的特征选择方法:平均减少杂质和平均减少准确性。

平均减少杂质

随机森林由许多决策树组成。决策树中的每个节点都是单个功能的条件,旨在将数据集分为两个,以便相似的响应值最终位于同一集合中。选择(局部)最佳条件所基于的措施称为杂质。对于分类,通常是基尼杂质或信息增益/熵,而对于回归树则是方差。因此,当训练一棵树时,可以计算出每个特征将一棵树中的加权杂质减少多少。对于森林,可以平均每个特征的杂质减少量,并根据此度量对特征进行排名。

这是sklearn的“随机森林”实现(随机森林分类器和随机森林回归器)中暴露的功能重要性衡量指标。

from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rf = RandomForestRegressor()
rf.fit(X, Y)
print "Features sorted by their score:"
print sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), 
             reverse=True)

结果如下所示:

Features sorted by their score:
[(0.5298, 'LSTAT'), (0.4116, 'RM'), (0.0252, 'DIS'), (0.0172, 'CRIM'), (0.0065, 'NOX'), (0.0035, 'PTRATIO'), (0.0021, 'TAX'), (0.0017, 'AGE'), (0.0012, 'B'), (0.0008, 'INDUS'), (0.0004, 'RAD'), (0.0001, 'CHAS'), (0.0, 'ZN')]

使用基于杂质的排名时,需要牢记一些注意事项。首先,基于杂质减少的特征选择偏向于选择具有更多类别的变量(请参阅随机森林变量重要性评估中的偏见)。其次,当数据集具有两个(或多个)相关特征时,则从模型的角度来看,这些相关特征中的任何一个都可用作预测变量,而没有一个相对于其他特征的具体偏好。但是一旦使用了它们中的一个,其他的重要性就大大降低了,因为有效地去除了它们可以去除的杂质已经被第一个特征去除了。结果,它们的重要性将降低。当我们要使用特征选择来减少过度拟合时,这不是问题,因为删除大部分由其他特征重复的特征是有意义的。但是,在解释数据时,可能会得出错误的结论,即其中一个变量是强预测变量,而同一组中的其他变量则不重要,而实际上它们在与响应变量之间的关系非常接近。

由于在每个节点创建时随机选择了特征,因此这种现象的影响有所降低,但通常效果并未完全消除。在以下示例中,我们具有三个相关变量X0,X1,X2,并且数据中没有噪声,而输出变量只是这三个特征的总和:

size = 10000
np.random.seed(seed=10)
X_seed = np.random.normal(0, 1, size)
X0 = X_seed + np.random.normal(0, .1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X = np.array([X0, X1, X2]).T
Y = X0 + X1 + X2
  
rf = RandomForestRegressor(n_estimators=20, max_features=2)
rf.fit(X, Y);
print "Scores for X0, X1, X2:", map(lambda x:round (x,3),
                                    rf.feature_importances_)

结果如下所示:

Scores for X0, X1, X2: [0.278, 0.66, 0.062]

当我们计算特征重要性时,我们发现X1的重要性比X2高出10倍以上,而它们的“真实”重要性却非常相似。尽管数据无噪声,但仍 会发生这种情况,我们使用20棵树,特征的随机选择(在每个分割处仅考虑三个特征中的两个)和足够大的数据集。

但是要指出的一件事是,解释相关变量的重要性/等级的困难不是特定于随机森林的,而是适用于大多数基于模型的特征选择方法。

平均降低精度

另一种流行的特征选择方法是直接测量每个特征对模型准确性的影响。一般的想法是置换每个特征的值并测量多少置换会降低模型的准确性。显然,对于不重要的变量,排列对模型精度应该几乎没有影响,而对重要变量进行排列应该会大大降低模型准确性。

该方法没有直接在sklearn中公开,但是实现起来很简单。继续前面的对波士顿住房数据集中的要素进行排名的示例:

from sklearn.cross_validation import ShuffleSplit
from sklearn.metrics import r2_score
from collections import defaultdict
 
X = boston["data"]
Y = boston["target"]
 
rf = RandomForestRegressor()
scores = defaultdict(list)
 
#crossvalidate the scores on a number of different random splits of the data
for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
    X_train, X_test = X[train_idx], X[test_idx]
    Y_train, Y_test = Y[train_idx], Y[test_idx]
    r = rf.fit(X_train, Y_train)
    acc = r2_score(Y_test, rf.predict(X_test))
    for i in range(X.shape[1]):
        X_t = X_test.copy()
        np.random.shuffle(X_t[:, i])
        shuff_acc = r2_score(Y_test, rf.predict(X_t))
        scores[names[i]].append((acc-shuff_acc)/acc)
print "Features sorted by their score:"
print sorted([(round(np.mean(score), 4), feat) for
              feat, score in scores.items()], reverse=True)

结果如下所示:

Features sorted by their score:
[(0.7276, 'LSTAT'), (0.5675, 'RM'), (0.0867, 'DIS'), (0.0407, 'NOX'), (0.0351, 'CRIM'), (0.0233, 'PTRATIO'), (0.0168, 'TAX'), (0.0122, 'AGE'), (0.005, 'B'), (0.0048, 'INDUS'), (0.0043, 'RAD'), (0.0004, 'ZN'), (0.0001, 'CHAS')]

在此示例中,LSTAT和RM是两个会对模型性能产生重大影响的功能:对它们进行置换会使模型性能分别降低约73%和约57%。请记住,尽管仅在对模型进行了所有这些功能训练(并取决于这些特征)之后才进行这些测量。这并不意味着如果我们训练没有这些功能的模型,模型性能就会下降那么大,因为可以使用其他相关的功能来代替。

原文地址:

Feature selection – Part I: univariate selection

http://blog.datadive.net/selecting-good-features-part-i-univariate-selection/

Selecting good features – Part II: linear models and regularization

http://blog.datadive.net/selecting-good-features-part-ii-linear-models-and-regularization/

Selecting good features – Part III: random forests

http://blog.datadive.net/selecting-good-features-part-iii-random-forests/

赞 赏 作 者

Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

推荐阅读:

一文读懂高并发情况下的常见缓存问题

用 Django 开发基于以太坊智能合约的 DApp

一文读懂 Python 分布式任务队列 celery

5 分钟解读 Python 中的链式调用

用 Python 创建一个比特币价格预警应用

▼点击成为社区会员   喜欢就点个在看吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值