数据科学:7个步骤解决任何数据科学问题

1. 入门

在外人看来,数据科学似乎是一门庞大而模糊的学科。当今的数据科学专家并没有上大学以获得数据科学学位(尽管现在许多大学都提供这些课程)。

第一代专业数据科学家来自数学,统计学,计算机科学和物理学等学科。

数据科学的“科学”部分是提出问题,生成假设,检查证据并制定解释证据的模型。

这些是任何人都可以学习的技能,并且比以往任何时候都有更多的资源来学习。

最好的资源之一是Kaggle 。他们的数据科学竞赛为所有人提供了一个挑战真实项目的平台。围绕这些挑战而形成的社区也是向他人学习的好地方。

当我从物理学家转变为数据科学家时,Kaggle是我自学新技能(尤其是使用scikit-learn之类的机器学习库)时所使用的资源之一。

本文来自《数据黑客》,登录官网可阅读更多精彩资讯和文章。

案例分析

在本文中,我将使用经典挑战“Titanic”来解释如何为任何数据科学问题找到一个成功的解决方案。

这项挑战的目的是建立一个模型,该模型可以根据乘客信息来预测是否存活。数据集包括乘客的姓名,年龄,性别,船舱等级和家庭信息,以及他们是否在灾难中幸免。

Kaggle提供训练数据和测试数据。训练数据具有生存的“基本事实”标签(是/否),但是测试数据不包含基本事实标签。Kaggle保留这些标签,并使用它们对您的模型进行评分。测试数据预测取决于您自己的预测,预测的准确性用于确定您在排行榜上的位置。

数据科学:7个步骤解决任何数据科学问题_第1张图片

工作环境

在开始数据科学项目之前,我建议您这样设置工作环境:

  1. 新建项目文件夹,带有用于存储数据的子文件夹。
  2. 单独的虚拟环境,安装标准数据科学库。

建议使用conda来管理Python环境。我首选的数据科学库是numpypandasmatplotlibseabornscikit-learn 。根据问题的性质,其他库(例如scipy )可能是相关的。深度学习挑战要安装TensorflowPyTorch

最后是加载数据。建议将数据从Kaggle下载到自己的计算机上,并存储到名为data的子文件夹中。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
pd.set_option('display.max_rows', 200)

import seaborn as sns

# Apply the default theme
sns.set()

# Assuming you've downloaded the data to your own machine and
# it resides in a subfolder called "data".
train_data = pd.read_csv('./data/train.csv')
test_data =  pd.read_csv('./data/test.csv')

2. 探索性数据分析

千万不要跳过这一步。

无论何时使用新数据,了解数据所包含的内容,变量的含义,所使用的单位,数据类型以及数据分布都非常重要。

这将帮助您更深入地理解数据,更容易提出假设并找到正确的解决方案。

训练集的前几行如下所示,“生存(Survived)”列是要预测的目标变量。

数据科学:7个步骤解决任何数据科学问题_第2张图片

Kaggle很好地解释了数据,并用表格解释了每个变量:

数据科学:7个步骤解决任何数据科学问题_第3张图片

其中大多数是不言自明的,但sibspparch需要更多解释:

sibsp :数据集以这种方式定义家庭关系
兄弟姐妹=兄弟,姐妹,继兄弟,继父
配偶=丈夫,妻子(情妇和未婚夫被忽略)
parch:数据集通过这种方式定义家庭关系…
父母=母亲,父亲
孩子=女儿,儿子,继女,继子
一些孩子只带一个保姆旅行,因此他们的parch = 0

按年龄或性别分类查看存活率

Seaborn擅长可视化数据分布,在本节中,我将以几种不同的方式检查数据。

我想知道妇女和儿童生存率更高的假设是否成立,因此我创建了以下图表,该图表显示了基于性别和年龄的乘客生存状况。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Apply the default theme
sns.set()

# Load the training data
train_data = pd.read_csv('./data/train.csv')

sns.catplot(data=train_data, kind="swarm", x="Sex", y="Age", hue="Survived")

数据科学:7个步骤解决任何数据科学问题_第4张图片

我们的假设是成立的,但是值得注意的是,有相当多的孩子没有幸存下来,而且有相当多的各个年龄段的男性确实幸存了下来,他们的幸存可能与什么事情相关?

船舱等级

也许舱位等级可以预测生存。让我们再看一下数据,这次按船舱等级分类。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Apply the default theme
sns.set()

# Load the training data
train_data = pd.read_csv('./data/train.csv')

sns.catplot(data=train_data, kind="swarm", x="Sex", y="Age", 
            col="Pclass", hue="Survived")

数据科学:7个步骤解决任何数据科学问题_第5张图片

上图根据年龄,性别和船舱等级(第1,第2,第3)列出生存情况。

在幸存的成年男子中,一等舱乘客的幸存比例更高。二等和三等旅客中也有成年男性幸存者,但相对而言,每组中的旅客人数并不多。

在没有幸存的妇女中,大多数是三等舱旅客。

这告诉我们性别,年龄和船舱等级都可能影响生存,但是每个组中都有离群值。目前尚不清楚这是随机的还是由于更细微的因素造成的。

登船地点

最后,让我们快速看一下这些乘客的登船地点,也许可以告诉我们一些事情。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Apply the default theme
sns.set()

# Load the training data
train_data = pd.read_csv('./data/train.csv')

sns.catplot(data=train_data, kind="count", x="Survived", col="Embarked")

数据科学:7个步骤解决任何数据科学问题_第6张图片

上图显示了在不同登船口岸的乘客的生存情况。

字母S,C和Q分别代表南安普敦,瑟堡和皇后镇。大部分乘客在南安普敦上船,在瑟堡上船的乘客似乎有更高的生存机会,但港口和生存之间似乎没有很强的相关性。

有了这些洞察,我们开始制定有关数据的假设,并在接下来的环节进行测试。如果不可视化数据,我们就不会有这些理解。

3. 清洗数据

即便在最理想的情况下,数据也很少是“干净的”,这意味着数据集可能存在缺失值或错误。有时候数据将以需要的格式进行转换,过滤或转换记录单位,然后才能进行下一步的工作。

如何清洗数据取决于具体的项目:

  • 图像可能需要重新缩放,旋转,色彩校正,平滑,锐化。
  • 音频可能需要过滤,重新制作,去噪或标准化。
  • 自然语言(文本)可能需要进行大小写校正,删除停用词,删除标点符号。

仔细查看Titanic数据,我们会发现一些问题:

X = train_data
y = train_data['Survived']

X.drop(columns = ['Survived'], inplace=True)

for col in X.columns:
    if X[col].isna().any():
        print('Column "{}" is missing data.'.format(col))

代码输出:

Column "Age" is missing data.  
Column "Cabin" is missing data.  
Column "Embarked" is missing data.

处理NAs和NaNs

这里我使用pandas数据框的.isna()方法检查缺失值。缺少数据或空值会产生“NA”代码,如果缺失数值型数据,则会生成“NaN”,表示“不是数字(Not a Number)”。

Titanic数据中包含许多缺失值,有时不知道一个人的年龄,他们所住的客舱(如果有)或登船口是什么。

我们的选择是:

  • 删除所有缺少值的行
  • 寻找合理的值来填补缺失值

每种方法都有其优点。在第一种情况下,我们不对数据做任何假设,而只是选择摆脱表中所有不完整的行。好处是不会往模型中注入太多主观假设,代价是训练数据可能不足。

在训练机器学习模型时,更多的数据总是更好。如果您有大量干净的数据,则最好丢弃所有不完整的样本。但是,如果表中的每一行都是宝贵的,那么最好找到合理的值来填补漏洞。

Titanic数据集不是很大,训练集只有不到1000名乘客,而且我们可能需要进一步细分训练数据以验证模型,从而导致训练集数据不足。

重新映射分类数据

机器学习模型需要数值输入,但是很多Titanic数据是分类变量,我们需要以某种方式将分类变量转换为数值变量。

Sex列只有两个值,即femalemale ,我们可以将它们重新映射为01

train_data.Sex = train_data.Sex.map({‘female’: 0, ‘male’: 1})

下面将使用“独热编码(one-hot encoding)”技术来处理具有多于两个类别的分类变量,例如登船地点(有3种类别)。

估算缺失值

通过一些合理的假设,我们实际上可以很好地填补数据中的缺失值。

年龄

我们可以采用好几种策略,例如简单地将所有乘客的平均年龄估算为缺失值。

但是我们可以做得更好。

我的策略是使用每个船舱等级的平均乘客年龄。

for pclass, grp in X.groupby('Pclass'):
    print('Class:', pclass, '-- Median Age:', grp.Age.median())

结果如下:

Class: 1 -- Median Age: 37.0  
Class: 2 -- Median Age: 29.0  
Class: 3 -- Median Age: 24.0

头等舱的乘客往往年龄更大,这并不令人惊讶,进入二等舱和三等舱,年龄会有所下降。

对于缺失年龄的观测值,我用该观测值所属的船舱等级的年龄中位数来填补。

def impute_age(row):
    if row['Pclass'] == 1:
        age = 37.0
    elif row['Pclass'] == 2:
        age = 29.0
    elif row['Pclass'] == 3:
        age = 24.0
    return age

missing_ages = X.Age.isna()
X.loc[missing_ages, 'Age'] = X[missing_ages].apply(lambda row: impute_age(row), axis=1)

登船港口

该变量的缺失值较少,最常见的登船点是南汉普顿,因此在其他条件相同的情况下,最有可能乘客会登上那里,所有级别的乘客都是如此。

missing_embarked = X.Embarked.isna()

X.loc[missing_embarked, 'Embarked'] = 'S'

从船舱到甲板

我们表中的许多行都包含一个舱号,最初尚不清楚如何利用此信息,但我们可以根据船舱号来确定船甲板,例如,“C22”在Deck C上。

客舱大多位于B到F甲板上,有关轮船布局的一些信息可以在这里找到,该页面还指示可以在哪里找到一等舱,二等舱和三等舱。

对于已知船舱号的乘客,我用它来推断甲板。

对于没有船舱号的乘客,我根据他们的票价来推断他们最可能乘坐的甲板。

我在数据框中创建了一个名为“Deck”的新列,并在其中写入了所有推断出的甲板信息。现在可以删除“Cabin”列。

def infer_deck(row):
    if type(row['Cabin']) == str:
        deck = str(row['Cabin'])[0]
    else:
        deck = 'Unknown'
    return deck

X['Deck'] = X.apply(lambda row: infer_deck(row), axis=1)

for pc, grp in X.groupby('Pclass'):
    print('\n Class:', pc)
    print(grp['Deck'].value_counts())
    
# For each class, impute missing deck by deck layout inferred from
# https://www.dummies.com/education/history/titanic-facts-the-layout-of-the-ship/
# Pclass 1: 'C'
# Pclass 2: 'E'
# Pcasss 3: 'F'
def infer_deck_v2(row):
    if row['Pclass'] == 1:
        deck = 'C'
    elif row['Pclass'] == 2:
        deck = 'E'
    else:
        deck = 'F'
    return deck

unknown_decks = X['Deck'] == 'Unknown'
X.loc[unknown_decks, 'Deck'] = X[unknown_decks].apply(lambda row: infer_deck_v2(row), axis=1)

X.drop(['Cabin'],axis=1, inplace=True)

输出为:

Class: 1  
C          59  
B          47  
Unknown    40  
D          29  
E          25  
A          15  
T           1  
Name: Deck, dtype: int64  
  
Class: 2  
Unknown    168  
F            8  
D            4  
E            4  
Name: Deck, dtype: int64  
  
Class: 3  
Unknown    479  
F            5  
G            4  
E            3  
Name: Deck, dtype: int64

对于所有带有未知甲板的乘客,我根据他们的乘客等级将他们分配到一个甲板。

解读票号

我花了大量时间研究可以从ticket列中收集哪些信息。

您会注意到某些票证带有前缀,例如“ SC/PARIS”,后跟数字。前缀和数字都可以告诉我们一些信息,我的猜测是前缀指示票务供应商,有时候可以通过票号推断出一起旅行的人群。

我对前缀数据做了很多深层清理和推断,最后我还是删除了它,因为它似乎并没有预测价值。如果找到了使用该变量来改进模型的方法,请发表评论。

Kaggle论坛上对此主题进行了很好的讨论。

4. 假设检验

现在我们已经清理了数据,可以尝试一些简单的测试。让我们分离一些测试数据,这些数据可以用来检验我们的假设。对于这些分离的测试数据,我们知道真实的目标变量标签,因此可以测量预测的准确性。

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

XX = X[['Age','Sex']]

std_scaler = StandardScaler()
XX = std_scaler.fit_transform(XX)

X_train, X_test, y_train, y_test = train_test_split(XX, y, test_size=0.25, 
                                                    random_state=42)

年龄和性别

我们知道Titanic的幸存者逃离了救生艇,而这些(我们假设)将优先容纳妇女和儿童。仅凭这两个变量,是否能够准确预测生存?

使用逻辑回归进行测试:

clf = LogisticRegression()

clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)

print(accuracy_score(y_pred, y_test))

预测精度为:

0.7847533632286996

78%的准确性非常好!正如预期的那样,这两个变量具有很高的预测性。

票价舱

接下来,我们可以假设,由于头等舱旅客的状态或他们的机舱靠近上层甲板,他们更有可能成为幸存者,所以让我们看看仅靠头等舱是否可以作为一个很好的预测指标。然后,我们将看看将其与年龄和性别相结合是否可以改善结果。

clf = LogisticRegression()

# Need to split Pclass into 3 separate binary columns
X = pd.concat([X, pd.get_dummies(X['Pclass'], prefix='Pclass')], 
               axis=1)

X.drop(['Pclass'],axis=1, inplace=True)

# Keep only the class columns
XX = X[['Pclass_1', 'Pclass_2', 'Pclass_3']]

std_scaler = StandardScaler()
XX = std_scaler.fit_transform(XX)

X_train, X_test, y_train, y_test = train_test_split(XX, y, test_size=0.25, 
                                                    random_state=42)

clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

print('Using Pclass as the sole predictor, our accuracy:')
print(accuracy_score(y_pred, y_test))


XX = X[['Age', 'Sex', 'Pclass_1', 'Pclass_2', 'Pclass_3']]

std_scaler = StandardScaler()
XX = std_scaler.fit_transform(XX)

X_train, X_test, y_train, y_test = train_test_split(XX, y, test_size=0.25, 
                                                    random_state=42)

clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

print('\nUsing Pclass, age, and sex as predictors, our accuracy:')
print(accuracy_score(y_pred, y_test))

Pclass变量进行独热编码,我将在下面说明独热编码的重要性。测试结果为:

Using Pclass as the sole predictor, our accuracy:  
0.6995515695067265  
  
Using Pclass, age, and sex as predictors, our accuracy:  
0.7937219730941704

使用船舱等级作为唯一的预测指标,我们的分类器的准确性几乎达到70%。结合年龄和性别,结果略有改善:分别为79%和78%。这种差异不是很大,可能是噪音。

这些实验告诉我们,生存很大程度上取决于年龄,性别和社会经济地位。仅这三个因素就可能使我们对生存情况有一个较好的预测。

但是要想将准确率提升几个百分点,需要进行一些特征工程(feature engineering)。

5. 特征工程

出色的特征工程通常会将专家与新手数据科学家区分开来。任何人都可以使用现成的软件库,用几行Python代码训练机器学习模型,并使用它进行预测。但是,数据科学不仅仅涉及模型选择。您需要为该模型提供高质量的预测特征。

设计新特征

特征工程通常意味着创建新特征,以帮助您的机器学习模型做出更好的预测。有一些工具可以使这一过程自动化,但是最好先深入考虑数据以及可能导致目标结果的其他因素。

在Titanic数据集中,我们有一些关于家庭出行的信息。“sibsp”和“parch”向我们介绍了乘客的兄弟姐妹,配偶,父母和子女的数量。我们可以创建一个新变量“ Family Size”,它是“ sibsp”和“ parch”的总和。

X['Family Size'] = X['SibSp'] + X['Parch']

许多Kagglers还会创建一个名为“not_alone”的变量,它是一个二进制标识符,描述乘客是否自己旅行。

独热编码

该数据集包含许多分类数据,例如登船口岸有3个标签:瑟堡,皇后镇和南安普敦。ML模型需要数值数据,因此我们必须将分类变量映射为数值变量:

{'Cherbourg': 1, 'Queenstown': 2, 'Southampton': 3}

考虑一下这对机器学习模型的影响。南安普敦的重要性比瑟堡高3倍吗?不,那是荒谬的,每个港口都同样重要。

取而代之的是,我们对分类变量实施“独热编码(one-hot encoding)”,这将创建三个新列,每个港口对应一列,使用数字0或1表示乘客是否登上了特定的港口。

数据科学:7个步骤解决任何数据科学问题_第7张图片

我们可以对其他分类变量(例如deck)执行相同的操作。在数据集中,性别也是一个分类变量,但是由于标签是“女性”或“男性”,因此只使用0/1来表示,无需创建新列。

独热编码的一个主要缺点是它会创建许多新列,每列均视为独立特征,更多特征并不总是一件好事。我们希望观测值的数量大大超过特征的数量,这可以防止过度拟合。

分组

一些Kagglers发现,针对年龄或票价范围创建单独的分组很有帮助。当试图登上救生艇时,船员可能不是在询问年龄,而是在考虑“婴儿”,“孩子”,“年轻”,“老人”等年龄类别。您可以创建类似的分类变量,看看这是否对您的模型有所帮助。我将保留年龄和票价变量不变。

6. 模型选择

上述步骤实际上是最难的部分,约占工作量的80%至90%。

接下来的几个步骤通常更轻松,更有趣。我们可以尝试不同的机器学习模型,以查看它们的性能如何,并选择有前途的模型进行进一步的优化。

由于我们只是在尝试预测二元变量“survival”,因此任何二元分类器都将起作用,scikit-learn提供了很多选择。

一些最受欢迎的分类器是:

  • 逻辑回归
  • 决策树
  • 随机森林
  • 自适应增强(AdaBoost)
  • XGBoost

最后一个XGBoost不是scikit-learn一部分,因此您必须单独安装它。

让我们获取训练数据,选择一个分类器,然后使用k折交叉验证进行测试。

交叉验证是一种技术,在保留剩余数据的同时对模型进行训练,可以忽略一小部分数据。然后针对遗漏的数据测试模型的准确性。将这个过程重复k次,每次都会随机抽取剩余数据的一部分。

在上述分类器中,逻辑回归和决策树最容易理解。随机森林是由许多决策树构成的整体模型。AdaBoost和XGBoost属于更高级的模型,其中XGBoost在Kagglers中非常受欢迎。

我不会在本文中介绍每个分类器的原理,这些信息很容易在网上找到。

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_validate
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier
import xgboost as xgb

X.drop(columns=['PassengerId', 'Name'], inplace=True)

std_scaler = StandardScaler()
X = std_scaler.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, 
                                                    random_state=42)


def assess_model(y_test, y_pred):
    scores = cross_validate(clf, X, y, cv=5,
                            scoring=('accuracy'),
                            return_train_score=True)
    
    train_avg = np.mean(scores['train_score']) 
    train_std = np.std(scores['train_score'])
    
    test_avg = np.mean(scores['test_score'])
    test_std =np.std(scores['test_score'])

    print('Average Train Accuracy: {:5.3f} ±{:4.2f}'.format(train_avg, train_std))
    print('Average Test Accuracy: {:5.3f} ±{:4.2f}'.format(test_avg, test_std))
    
    return
    
  
clf = LogisticRegression(class_weight='balanced', max_iter=1000, solver='lbfgs')
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
assess_model(y_test, y_pred)

clf = DecisionTreeClassifier(random_state=0)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
assess_model(y_test, y_pred)

clf = RandomForestClassifier(random_state=0)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
assess_model(y_test, y_pred)

clf = AdaBoostClassifier(random_state=0)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
assess_model(y_test, y_pred)

clf = xgb.XGBClassifier()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
assess_model(y_test, y_pred)

assess_model函数使用5折交叉验证测试每个分类器的准确性,结果如下:

# Logistic regression  
Average Train Accuracy: 0.802 ±0.01  
Average Test Accuracy: 0.780 ±0.02

# Decision tree  
Average Train Accuracy: 0.974 ±0.00  
Average Test Accuracy: 0.781 ±0.02

# Random forest  
Average Train Accuracy: 0.974 ±0.00  
Average Test Accuracy: 0.791 ±0.03

# AdaBoost  
Average Train Accuracy: 0.832 ±0.00  
Average Test Accuracy: 0.804 ±0.02

# XGBoost  
Average Train Accuracy: 0.964 ±0.00  
Average Test Accuracy: 0.820 ±0.03

要注意一点,每个分类器都使用了默认超参数。

有时分类器可以在训练集上获得约97%的准确性,这看起来很棒,但很可能是过拟合的结果。测试集的预测精度才是我们关心的指标。

在这些分类器中,XGBoost具有最高的准确性。

7. 模型优化

大多数机器学习模型具有可调整的参数,这些参数通常会影响模型的准确性。对于每个问题,这些参数的最佳性能值都不同。

在ML中,这些参数通常称为“超参数”,调整超参数与其说是一门科学倒不如是一门艺术。

有一些工具可以帮助您进行调整。 TPOT就是一个例子。为了简单起见,我们将执行一个简单的网格搜索,并手动测试整个范围的合理超参数值,以查看哪个参数可以给我们带来最佳结果。

from sklearn.model_selection import GridSearchCV

param_grid = {'bootstrap': [True],
     'max_depth': [2, 6, None],
     'max_features': ['auto', 'log2'],
     'min_samples_leaf': [1, 2, 3, 5],
     'min_samples_split': [2, 4, 6],
     'n_estimators': [100, 350]
    }
     

forest_clf = RandomForestClassifier()

forest_grid_search = GridSearchCV(forest_clf, param_grid, cv=5,
                                  scoring="accuracy",
                                  return_train_score=True,
                                  verbose=True,
                                  n_jobs=-1)

forest_grid_search.fit(X_train, y_train)

这段代码要花一些时间才能运行,因为它会尝试所有不同的超参数组合。

print(forest_grid_search.best_params_)

print(forest_grid_search.best_estimator_)

print(forest_grid_search.best_score_)

结果如下:

{'bootstrap': True,  
 'max_depth': 6,  
 'max_features': 'auto',  
 'min_samples_leaf': 2,  
 'min_samples_split': 4,  
 'n_estimators': 100}

RandomForestClassifier(max_depth=6, min_samples_leaf=2, min_samples_split=4)

0.830804623499046

结论

在Kaggle Titanic挑战中,如果不作弊,很难获得超过83%的准确率。我提到了其他几种提高预测精度的潜在方法,例如对年龄或票价分类,以及根据阶级和性别来估算缺失的年龄值。您可以尝试这些方法,看看它们是否可以提高准确性。

解决数据科学问题是一个迭代的过程,即从头开始,了解您的数据,进行清洗,然后反复测试不同的模型并添加更多特征,直到获得良好的性能为止。最终您还可以优化最佳模型,进一步提升预测精度。

来源:Medium

作者:Mikhail Klassen

翻译校对:数据黑客

原文标题:How to Structure Your Data Science Workflow

数据黑客:专注金融大数据,聚合全网最好的资讯和教程,提供开源数据接口。

我们聚合全网最优秀的资讯和教程:

  1. 金融大数据
  2. 机器学习/深度学习
  3. 量化交易
  4. 数据工程
  5. 编程语言,Python,R,Julia,Scala,SQL

我们提供开源数据接口:

  1. 下载国内和国外海量金融数据
  2. API接口,将数据整合到您的平台

你可能感兴趣的:(python,机器学习,大数据,机器学习,python,人工智能)