2015 年 7 月
第 30 卷,第 7 期
R 编程语言 - 面向 C# 程序员的 R 的简介
数据科学家和程序员使用 R 语言进行统计计算。部分原因在于软件系统收集的数据量不断增加,而且需要分析该数据,因此在使用 C# 的同事中,R 是增长最快速的技术之一。熟悉 R 是对您的技术技能集的宝贵补充。
R 语言属于 GNU 项目,并且是免费的软件。R 从名为 S(表示“统计”)的语言中派生而来,它在 20 世纪七十年代创建于贝尔实验室中。有很多关于 R 的优秀在线教程,但是大多数教程假设您是正在学习统计的大学生。本文旨在帮助 C# 程序员尽可能快地掌握 R。
了解本文所述观点的最佳方法是查看图 1 中的示例 R 会话。示例会话含有两个不相关的主题。第一少数组命令显示所谓的卡方检验,对均匀分布进行测试。第二组命令显示一个线性回归的示例,在我看来这是统计计算的入门方法。
图 1 示例 R 会话
R 网站位于 r-project.org。该网站含有指向几个镜像站点的链接,您可以在这些站点中下载并安装 R。该安装是简单的自提取可执行文件。R 受到 Windows XP 和更高版本的官方支持,并且还可在最常用的非 Windows 平台上使用。我已经在 Windows 7 和 Windows 8 机器上安装了 R,没有遇到任何问题。默认情况下,该安装过程会向您提供 32 位版本和 64 位版本。
本文假设您至少掌握了中级 C# 编程技能(因此,您可以了解 C# 和 R 之间的相同和不同之处的说明),但是不假设您对 R 非常了解。本文展示了一个完整的 C# 演示程序,该演示程序同样可从本文随附的下载文件中获取。
使用 R 进行卡方检验
查看图 1,首先要注意的是,使用 R 与使用 C# 之间的区别很大。虽然可以编写 R 脚本,但通常在命令 shell 中的交互模式中使用 R。第一个 R 示例是一个分析,用于查看常规的六面骰子是否公平合理。投掷多次以后,公平合理的骰子各个面出现的次数几乎相同。
Shell 中的令牌 (>) 指明 R 提示符。图 1 中输入的首个语句以 (#) 字符开头,这是指明一个备注的 R 令牌。
该示例会话中的第一个实际命令是:
> observed <- c(20, 28, 12, 32, 22, 36)
这会使用 c(表示串联)函数创建一个名为“observed”的向量。向量会保留相同数据类型的对象。大致等效 C# 语句是:
var observed = new int[] { 20, 28, 12, 32, 22, 36 };
第一个值 20 表示一点出现的次数,28 表示二点出现的次数,然后以此类推。六次计数的总和是 20 + 28 + . . + 36 = 150。您预期的公平合理的骰子应该是每个结果出现 25 次(150/6 = 25)。但是,观察得出的三点出现的次数为 12,这出奇的低。
创建向量之后,使用 chisq.test 函数执行卡方检验:
> chisq.test(observed)
在 R 中,通常使用点 (.) 字符(而非下划线 (_) 字符)来创建更易于读取的变量和函数名称。调用 chisq.test 函数的结果是一小段文本:
指定概率的卡方检验
data: observed
X-squared = 15.28, df = 5, p-value = 0.009231
在 C# 术语中,虽然大多数 R 函数返回可被忽略的数据结构,但是也包含很多公开结果的 Console.WriteLine 等效语句。请注意,您自行决定解读 R 输出的含义。在下文中,我将向您演示如何使用原始(不含库)C# 代码来创建等效的卡方检验。
在此示例中,15.28 的“X 平方”值是计算后的卡方检验统计(希腊字母 X 表示大写字母 X)。如果骰子公平合理,则值 0.0 表示观察的值与您预期的值完全相同。如果骰子公平合理,则卡方的值越大表示观察的计数不发生的可能性越大。df 值 5 是指“自由度”,这比观察值的数量少一。对于此次分析,该 df 不是太重要。
如果骰子公平合理,则 p 值 0.009231 是指观察的计数偶然发生的概率。由于 p 值非常小(小于 1 个百分比),您得出的结论是观察的值极不可能偶然发生,有统计数据证明该骰子有问题,很有可能存在偏差。
使用 R 进行线性回归分析
图 1 中的第二组语句显示一个线性回归的示例。线性回归是用于描述数字变量(在统计学中称为“因变量”)和一个或多个可以是数字变量也可以是类别变量的解释变量(称为“自变量”)之间关系的统计方法。如果只有一个解释/预测自变量,则该方法称为简单的线性回归。如果像本演示示例中这样有两个或多个自变量,则该方法称为多重线性回归。
在进行线性回归分析之前,我使用以下内容在目录 C:\IntroToR 中创建了一个名为 DummyData.txt 的文本文件。该文件含有 8 个项,且用逗号分隔:
Color,Length,Width,Rate
blue, 5.4, 1.8, 0.9
blue, 4.8, 1.5, 0.7
blue, 4.9, 1.6, 0.8
pink, 5.0, 1.9, 0.4
pink, 5.2, 1.5, 0.3
pink, 4.7, 1.9, 0.4
teal, 3.7, 2.2, 1.4
teal, 4.2, 1.9, 1.2
该文件表示花卉数据,其中包括花朵的颜色、花瓣的长度和宽度以及生长速率。其理念是根据“颜色”、“长度”和“宽度”值预测“速率”值(位于最后一列中)。在一个注释语句之后,线性回归分析中的前三个 R 命令是:
> setwd("C:\\IntroToR")
> data <- read.table("DummyData.txt",
header=TRUE, sep=",")
> print(data)
第一个命令集设置工作目录,因此我就不必获得访问源数据文件的资格了。虽然使用 (\\) 令牌对于 C# 很常见,但是我使用的是 (/),因为这在非 Windows 平台上很常见。
第二个命令将数据加载到名为 data 的表对象中的内存中。请注意,R 使用已命名的参数。标头参数告知第一行是标头信息(TRUE,或用 T 缩写),还是不是标头信息(FALSE 或 F)。R 区分大小写,并且您通常可以使用 (<-) 或 (=) 操作符来分配值。选择哪个操作符全凭个人偏好。通常,我使用 (<-) 分配对象,使用 (=) 分配参数值。
sep(分隔符)参数指明如何分隔每行上的参数。例如,(\t) 表示制表符分隔的值,(" ") 表示空格分隔的值。
打印函数显示内存中的数据表格。打印函数含有很多其他参数。请注意,图 1 中的输出显示起始值为 1 的数据项索引。对于数组、矩阵和对象索引,R 是基于 1 的语言,而不是像 C# 语言一样,是基于 0 的。
使用以下两个 R 命令执行线性回归分析:
> model <- lm(data$Rate ~ (data$Color + data$Length + data$Width))
> summary(model)
您可以将第一个命令理解为“存储到名为 model 的对象中,其中在 Im(线性模型)函数分析的结果中,要预测的因变量位于表格对象 (data$Rate) 的“速率”列,而预测自变量是颜色、长度和宽度”。 第二个命令表示“只显示名为 model 的对象中存储的基本分析结果”。
lm 函数会生成大量的信息。假设您要预测输入值为“Color = pink,Length = 5.0 和 Width = 1.9 ”时的 Rate 值。(请注意,这对应于数据项 [4],它含有一个为 0.4 的实际 Rate 值)。 要进行预测,您最好使用“估计值”列中的值:
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.14758 0.48286 -0.306 0.77986
data$Colorpink -0.49083 0.04507 -10.891 0.00166 **
data$Colorteal 0.35672 0.09990 3.571 0.03754 *
data$Length 0.04159 0.07876 0.528 0.63406
data$Width 0.45200 0.11973 3.775 0.03255 *
如果 X 表示自变量值,而 Y 表示预测 Rate,那么:
X = (blue = NA, pink = 1, teal = 0, Length = 5.0, Width = 1.9)
Y = -0.14758 + (-0.49083)(1) + (0.35672)(0) + (0.04159)(5.0) + (0.45200)(1.9)
= -0.14758 + (-0.49083) + (0) + (0.20795) + (0.85880)
= 0.42834
请注意,预测 Rate 为 0.43,这非常接近实际速率 0.40。
总之,要使用该模型进行预测,您需要计算估计值乘以其对应 X 值后产生的线性总和。该 Intercept 值是一个不与任何变量相关的常量。当您含有类别解释变量时,请去掉其中一个值(本例中的蓝色部分)。
输出显示底部的信息表明自变量、颜色、长度和宽度解释自变量 Rate 的合理程度:
Residual standard error: 0.05179 on 3 degrees of freedom
Multiple R-squared: 0.9927, Adjusted R-squared: 0.9829
F-statistic: 101.6 on 4 and 3 DF, p-value: 0.00156
多重 R 平方值 (0.9927) 是自变量的线性结合所解释的因变量中的不均匀率。稍有不同的是,R 平方是介于 0 和 1 之间的值,其中值越高表示预测模型越好。此处,R 平方值非常高,这表示颜色、长度和宽度可以非常准确地预测速率。F-统计量、调整后的 R 平方值和 p 值是模型拟合的其他度量值。
此示例中的其中一个要点是,在使用 R 进行编程时,您目前为止最大的挑战在于理解语言函数背后的统计信息。大多数人以一种增量方式来学习 R,即在需要回答一些特定的问题时,一次增加一种技术的知识。C# 类比是在学习 Collections.Generic 命名空间(如 Hashtable 和 Queue 类)中的各种收集对象。大多数开发人员一次学习一种数据结构(而不是尝试记住有关所有类的信息),然后才在项目中使用其中的任何信息。
另一个卡方检验
图 1 中的卡方检验类型通常称为均匀分布检验,这是因为它测试观察数据是否都含有相同的数量;即,这些数据是否均匀分布。还有其他几种卡方检验,包括独立性的卡方检验。
假设您有一组人(100 人),并且您非常想知道性别(男性、女性)是否独立于政党从属关系(民主党、共和党等)。假设您的数据位于可能性矩阵中,如图 2 中所示。它显示的结果是,男性是共和党人的可能性比女性大。
图 2 可能性矩阵
民主党 | 共和党 | 其他 | ||
男 | 15 | 25 | 10 | 50 |
女 | 30 | 15 | 5 | 50 |
45 | 40 | 15 | 100 |
要使用 R 测试两个因素(性别和从属关系)在统计上是独立的,您首先通过以下命令将该数据放入数字矩阵中:
> cm <- matrix( c(15,30,25,15,10,5), nrow=2, ncol=3 )
请注意,在 R 中,矩阵数据是按照列(顶部到底部,左侧到右侧)进行存储的,而不是按照行(左侧到右侧,顶部到底部)进行存储,这和在 C# 中一样。
R 卡方检验命令是:
> chisq.test(cm)
该检验的 p 值结果是 0.01022,这表示在 5% 的显著性水平上,两个因素不是互相独立的。换句话说,性别和所属关系之间存在统计关系。
请注意,在第一个卡方骰子示例中,该输入参数是向量,但是在第二个性别-从属关系示例中,该输入是矩阵。chisq.test 函数总共含有 7 个参数,一个(向量或矩阵)是必需的,后面跟着六个可选的已命名参数。
像 Microsoft .NET Framework 命名空间中的很多 C# 方法一样,大多数 R 函数过度重载。在 C# 中,通常通过使用多个名称相同、参数不同的方法来实施重载。泛型的使用也是一种重载形式。在 R 中,使用带有很多可选的已命名参数的单个函数实施重载。
R 中的图形
作为一名 C# 程序员,当我想要制作一些程序输出数据的图形时,我通常会运行我的程序,复制输出数据,并通过 Ctrl+V 将该数据粘贴到记事本,以删除奇怪的控制字符,然后复制该数据,将其粘贴到 Excel,最终使用 Excel 创建一个图形。虽然这有点麻烦,但可适用于大多数情况下。
R 系统的一个优势在于它自带的生成图形的功能。查看图 3 中示例。在没有某种外接程序的情况下,这种类型的图形不可能在 Excel 中制作。
图 3 使用 R 的 3D 图形
除了图 1 中显示的 Shell 程序以外,R 还拥有一个半 GUI 接口 RGui.exe,可供您在制作图形时使用。
图 3 中的图形显示函数 z = f(x,y) = x * e^(-(x^2 + y^2))。用于生成图形的前三个 R 命令是:
> rm(list=ls())
> x <- seq(-2, 2, length=25)
> y <- seq(-2, 2, length=25)
该 rm 函数会删除内存中当前工作区中的对象。使用的命令是用于删除所有对象的充满魔法的 R 咒语。第二和第三个命令会创建 25 个值的向量,从 -2 到 +2(含 -2 和 +2)之间对空间进行均匀分配。我一直想要使用 c 函数进行代替。
接下来的两个命令是:
> f <- function(x,y) { x * exp(-(x^2
+ y^2)) }
> z <- outer(x,y,f)
第一个命令是显示如何使用函数关键字在 R 中定义函数。命名为 outer 的内置 R 函数可使用向量 X 和 Y 以及一个函数定义 f 来创建相应值的矩阵。结果是 25 x 25 的矩阵,其中每个单元格中的值是对应于 x 和 y 的函数 f 的值。
接下来的两个命令是:
> nrz <- nrow(z)
> ncz <- ncol(z)
nrow 和 ncol 函数会返回其矩阵参数中的行数或列数。此处,两个值都是 25。
接下来 R 命令会使用 colorRampPalette 函数来创建自定义颜色渐变调色板以对图形进行涂色:
> jet.colors <- colorRampPalette(c("midnightblue", "blue",
+ "cyan", "green", "yellow", "orange", "red", "darkred"))
在 R 中输入一个较长的行时,如果您按下 <Enter> 键,该系统将使光标跳到下一行,并放置一个 + 字符作为提示符,以指明您的命令尚未完成。接下来:
> nbcol <- 64
> color <- jet.colors(nbcol)
这两个命令的结果是一个名为 color 的向量,它保留 64 种不同的颜色值,范围从深蓝色到绿色和黄色,再到深红色。接下来:
> zfacet <- z[-1,-1] + z[-1,-ncz] + z[-nrz,-1] + z[-nrz,-ncz]
> facetcol <- cut(zfacet,nbcol)
如果您仔细查看图 3 中的图形,您将看到该图形的表面由 25 x 25 = 625 个小正方形或切面构成。前面的两个命令创建了一个 25 x 25 的矩阵,其中每个单元格中的值是 64 中颜色之一,用于相应的表层切面。
最终,使用 persp(透视图)函数显示 3D 图形:
> persp(x,y,z, col=color[facetcol], phi=20, theta=-35,
+ ticktype="detailed", d=5, r=1, shade=0.1, expand=0.7)
该 persp 函数含有很多可选的已命名参数。col 函数是要使用的颜色。参数 φ 和 θ 可设置图形的查看角度(左和右以及上和下)。参数 ticktype 控制值在 x、y 和 z 轴上的显示方式。参数 r 和 d 控制人眼到图形的感知距离以及感知 3D 效果。命名为 shade 的参数控制虚拟光源中的模拟阴影。命名为 expand 的参数控制图形的宽高比。虽然 persp 函数含有更多参数,但是此处使用的参数足够在大多数情况下使用。
此示例指出 R 含有非常强大和灵活的自带图形处理能力,但是它们的级别相对较低且需要投入大量的精力。
C# 中的卡方检验
要理解 R 语言分析和 C# 语言编程之间的相似性和不同之处,这对检查卡方检验的 C# 实施非常有帮助作用。此外,C# 代码是对您的个人代码库的有益补充。仔细查看图 4 中的 C# 演示程序。
图 4 使用 C# 进行卡方检验
该演示程序近似于图 1 中显示的 R 语言卡方骰子检验。请注意,C# 演示的输出值与 R 会话中的值完全相同。
为了创建演示程序,我启动了 Visual Studio,并新建了一个名为 ChiSquare 的 C# 控制台应用程序项目。在将模板代码加载到编辑器之后,我在解决方案资源管理器窗口中右键单击文件 Program.cs,并将其重命名为 ChiSquareProgram.cs,同时允许 Visual Studio 自动将 Program 类重命名为 ChiSquareProgram。
图 5 中列出了完整的 C# 演示程序。您将注意到该源代码比您预期的代码长。使用原始 C# 实施统计编程并非极其困难,但是代码很长。
图 5 C# 卡方演示程序
using System;
namespace ChiSquare
{
class ChiSquareProgram
{
static void Main(string[] args)
{
try
{
Console.WriteLine("\nBegin Chi-square test using C# demo\n");
Console.WriteLine(
"Goal is to see if one die from a set of dice is biased or not\n");
int[] observed = new int[] { 20, 28, 12, 32, 22, 36 };
Console.WriteLine("\nStarting chi-square test");
double p = ChiSquareTest(observed);
Console.WriteLine("\nChi-square test complete");
double crit = 0.05;
if (p < crit)
{
Console.WriteLine("\nBecause p-value is below critical value of " +
crit.ToString("F2"));
Console.WriteLine("the null hypothsis is rejected and we conclude");
Console.WriteLine("the data is unlikely to have happened by chance.");
}
else
{
Console.WriteLine("\nBecause p-value is not below critical value of " +
crit.ToString("F2"));
Console.WriteLine(
"the null hypothsis is accepted (not rejected) and we conclude");
Console.WriteLine("the observed data could have happened by chance.");
}
Console.WriteLine("\nEnd\n");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
} // Main
static void ShowVector(int[] v)
{
for (int i = 0; i < v.Length; ++i)
Console.Write(v[i] + " ");
Console.WriteLine("");
}
static double ChiSquareTest(int[] observed)
{
Console.WriteLine("Observed frequencies are: ");
ShowVector(observed);
double x = ChiSquareStatistic(observed);
Console.WriteLine("\nX-squared = " + x.ToString("F2"));
int df = observed.Length - 1;
Console.WriteLine("\ndf = " + df);
double p = ChiSquareProb(x, df);
Console.WriteLine("\np-value = " + p.ToString("F6"));
return p;
}
static double ChiSquareStatistic(int[] observed)
{
double sumObs = 0.0;
for (int i = 0; i < observed.Length; ++i)
sumObs += observed[i];
double expected = (int)(sumObs / observed.Length);
double result = 0.0;
for (int i = 0; i < observed.Length; ++i)
{
result += ((observed[i] - expected) *
(observed[i] - expected)) / expected;
}
return result;
}
public static double ChiSquareProb(double x, int df)
{
// x = a computed chi-square value. df = degrees of freedom.
// output = prob. the x value occurred by chance.
// So, for example, if result < 0.05 there is only a 5% chance
// that the x value occurred by chance and, therefore,
// we conclude that the actual data which produced x is
// NOT the same as the expected data.
// This function can be used to create a ChiSquareTest procedure.
// ACM Algorithm 299 and update ACM TOMS June 1985.
// Uses custom Exp() function below.
if (x <= 0.0 || df < 1)
throw new Exception("parameter x must be positive " +
"and parameter df must be 1 or greater in ChiSquaredProb()");
double a = 0.0; // 299 variable names
double y = 0.0;
double s = 0.0;
double z = 0.0;
double e = 0.0;
double c;
bool even; // Is df even?
a = 0.5 * x;
if (df % 2 == 0) even = true; else even = false;
if (df > 1) y = Exp(-a); // ACM update remark (4)
if (even == true) s = y; else s = 2.0 * Gauss(-Math.Sqrt(x));
if (df > 2)
{
x = 0.5 * (df - 1.0);
if (even == true) z = 1.0; else z = 0.5;
if (a > 40.0) // ACM remark (5)
{
if (even == true) e = 0.0;
else e = 0.5723649429247000870717135;
c = Math.Log(a); // log base e
while (z <= x)
{
e = Math.Log(z) + e;
s = s + Exp(c * z - a - e); // ACM update remark (6)
z = z + 1.0;
}
return s;
} // a > 40.0
else
{
if (even == true) e = 1.0;
else e = 0.5641895835477562869480795 / Math.Sqrt(a);
c = 0.0;
while (z <= x)
{
e = e * (a / z); // ACM update remark (7)
c = c + e;
z = z + 1.0;
}
return c * y + s;
}
} // df > 2
else
{
return s;
}
} // ChiSquare()
private static double Exp(double x) // ACM update remark (3)
{
if (x < -40.0) // ACM update remark (8)
return 0.0;
else
return Math.Exp(x);
}
public static double Gauss(double z)
{
// input = z-value (-inf to +inf)
// output = p under Normal curve from -inf to z
// e.g., if z = 0.0, function returns 0.5000
// ACM Algorithm #209
double y; // 209 scratch variable
double p; // result. called 'z' in 209
double w; // 209 scratch variable
if (z == 0.0)
p = 0.0;
else
{
y = Math.Abs(z) / 2;
if (y >= 3.0)
{
p = 1.0;
}
else if (y < 1.0)
{
w = y * y;
p = ((((((((0.000124818987 * w
- 0.001075204047) * w + 0.005198775019) * w
- 0.019198292004) * w + 0.059054035642) * w
- 0.151968751364) * w + 0.319152932694) * w
- 0.531923007300) * w + 0.797884560593) * y * 2.0;
}
else
{
y = y - 2.0;
p = (((((((((((((-0.000045255659 * y
+ 0.000152529290) * y - 0.000019538132) * y
- 0.000676904986) * y + 0.001390604284) * y
- 0.000794620820) * y - 0.002034254874) * y
+ 0.006549791214) * y - 0.010557625006) * y
+ 0.011630447319) * y - 0.009279453341) * y
+ 0.005353579108) * y - 0.002141268741) * y
+ 0.000535310849) * y + 0.999936657524;
}
}
if (z > 0.0)
return (p + 1.0) / 2;
else
return (1.0 - p) / 2;
} // Gauss()
} // Program class
} // ns
Main 方法几乎全部由 WriteLine 语句构成。基本的调用代码是:
int[] observed = new int[] { 20, 28, 12, 32, 22, 36 };
double p = ChiSquareTest(observed);
double crit = 0.05;
if (p < crit) {
// Messages
} else {
// Messages
}
C# 卡方检验接受观察值的数组,计算卡方统计值并显示该值;计算并显示自由度;同时计算和返回 p 值。
ChiSquareTest 方法调用三个帮助程序方法:
ShowVector(observed);
double x = ChiSquareStatistic(observed);
int df = observed.Length - 1;
double p = ChiSquareProb(x, df);
ShowVector 方法显示输出向量,这类似于呼应输入参数值的 R chisq.test 函数使用的方法。ChiSquareStatistic 方法返回计算后的卡方(R 输出中的“X 平方”),ChiSquareProb 方法使用从 ChiSquareStatistic 中的返回内容来计算可能性(R 输出中的“p 值”)。
ChiSquareStatistic 方法是对均匀分布的简单检验。卡方统计的统计方程是:
chi-squared = Sum( (observed - expected)^2 / expected )
那么,在计算卡方之前,您需要计算与观察值相关的预期值。如前所述,要实现此目的,您要在观察数组(演示中为 150)中将计数值相加,然后用总和除以数组中的值的数量(演示中为 6),最终得出所有单元格的预期值 (25)。
编写卡方检验的最困难之处在于 p 值的计算。如果您浏览图 5 中的 ChiSquareProb 方法的定义,将很快就会意识到这需要深入的专业知识。而且,该方法要调用一个同样复杂的帮助程序方法(称为 Gauss)。
实际上,编码复杂的数字函数非常容易,因为数十年来已求解了大多数函数。我最常使用的两个资源是:美国计算机协会 (ACM) 收集的算法存储库,以及 Abramowitz 和 Stegun 所著的《Handbook of Mathematical Functions》(数学函数手册)(1965 年由多弗尔出版社出版),该书非常出名,因此经常直接被称为“A&S”。 这两类参考内容都可以免费从 Web 上的多个位置中获取。
例如,ChiSquareProb 方法实施 ACM 算法 #299,帮助程序方法 Gauss 实施 ACM 算法 #209。ACM #209 的备用方法是 A&S 方程 #7.1.26 以及简单的包装语句。
虽然现有 C# 库中实施了 ACM 算法和 A&S 中的很多函数,但是,这些库中的大多数库都很大。如果可能的话,我更喜欢使用我需要的方法从头开始进行编码,从而避免外部依赖。
几点注释
虽然本文只展示了 R 语言的一小部分,但是足以让您振奋并继续下去。C# 程序员常常通过两种方式接触到 R。第一,您可能使用 R 来执行自己的统计分析。如本文所示,使用 R 非常简单(假设您已掌握基本的统计学)。第二,您可能需要与将 R 作为其主要语言的其他人进行互动,因此需要了解 R 环境和术语。
有很多 R 语言集成开发工具可供您使用。一个众所周知的项目是 RStudio。一般来说,我更喜欢使用原生的 R 控制台。多款 Microsoft 产品可与 R 结合使用,例如,Microsoft Azure 机器学习服务可以使用 R 语言脚本,并且我推测,在未来的某个时间将向 Visual Studio 中添加对 R 的支持。
Dr.James McCaffrey* 供职于华盛顿地区雷蒙德市沃什湾的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和 Bing。Scripto可通过 jammc@microsoft.com 与 McCaffrey 取得联系。*
衷心感谢以下 Microsoft Research 技术专家对本文的技术审阅:Dan Liebling 和 Robin Reynolds-Haertle
Robin Reynolds-Haertle 编写使用 Visual Studio 进行跨平台开发的文档。她目前对 .NET Core、C#、Swift 和 R 非常关注。