如何使用C# DataFrame和XPlot.Ploty

2,084 阅读7分钟

对于Python编程语言,Pandas是一个高效和流行的数据分析工具,特别是它的Dataframe,用于操作和显示数据。对于.NET编程语言,我们可以使用NuGet中的DeedleMicrosoft.Data.Analysis包,它也提供了一个用于操作、转换和显示数据的DataFrame类。

这个例子着重于Microsoft.Data.Analysis包,展示了Jupyter Notebook中DataFrame类的一些基本功能。

它还使用了XPlot.Plotly包,它是F#数据可视化包,为Dataframe中的数据绘制图表。源代码可在GitHub上找到。

前提条件

要运行本文中的例子,请参考这篇在Jupyter Notebook中使用.NET Core的文章,以设置Jupyter Notebook来支持.NET编程语言。

安装软件包

Microsoft.Data.Analysis包在Nuget中可用,因此可以使用dotnet-interactive**#r** magic命令从NuGet中安装该包。

运行下面的命令来安装Microsoft.Data.Analysis软件包0.4.0版本。

#r "nuget:Microsoft.available Data.Analysis,0.4.0"

参考命名空间

本文使用了以下四个包的类。因此,它使用using语句来引用这些包:

  • XPlot.Plotly。一个用于F#和.NET编程语言的跨平台数据可视化包
  • Microsoft.Data.Analysis。一个易于使用和高性能的数据分析和转换库
  • System.Linq:支持使用语言集成查询的类和接口。
  • Microsoft.AspNetCore.Html:用于操作HTML内容的类型
using XPlot.Plotly;
using Microsoft.Data.Analysis;
using System.Linq;
using Microsoft.AspNetCore.Html;

将一个DataFrame渲染成一个HTML表

默认情况下,一个DataFrame被渲染成一个有一行和两列的HTML表(Columns and Rows)。

Columns vs. Rows

这可以通过为DataFrame注册自定义格式器来重写。下面的代码为DataframeDataFrameRow注册了自定义格式器,以在HTML表格中呈现数据。

Custom Formatting

它只显示前100行。这可以通过修改take变量的值来改变。

Formatter<DataFrame>.Register((df, writer) =>
{
    var headers = new List<IHtmlContent>();
    headers.Add(th(i("index")));
    headers.AddRange(df.Columns.Select(c => (IHtmlContent) th(c.Name)));
    var rows = new List<List<IHtmlContent>>();
    var take = 100;
    for (var i = 0; i < Math.Min(take, df.Rows.Count); i++)
    {
        var cells = new List<IHtmlContent>();
        cells.Add(td(i));
        foreach (var obj in df.Rows[i])
        {
            cells.Add(td(obj));
        }
        rows.Add(cells);
    } 
    var t = table(
        thead(
            headers),
        tbody(
            rows.Select(
                r => tr(r))));
    writer.Write(t);    
    writer.Write(df.Rows.Count + " x "+df.Columns.Count);
}, "text/html");
 
Formatter<DataFrameRow>.Register((dataFrameRow, writer) =>
{
    var cells = new List<IHtmlContent>();
    cells.Add(td(i));
    foreach (var obj in dataFrameRow)
    {
        cells.Add(td(obj));
    }
    var t = table(
        tbody(
            cells));
    writer.Write(t);
}, "text/html");

创建DataFrame

数据框架列(DataFrameColumn

通过将DataFrameColumn对象的列表传递给DataFrame的构造函数,就可以创建一个DataFrame。

public DataFrame(params DataFrameColumn[] columns);
public DataFrame(IEnumerable columns);

下面的代码创建了一个有200行和2列的DataFrame。第一列包含日期,第二列包含随机整数。它调用PrimitiveDataFrameColumn构造函数来创建DataFrameColumn实例。

var start = new DateTime(2009,1,1);
Random rand = new Random();
var numDataPoint = 200

PrimitiveDataFrameColumn<DateTime> date = new PrimitiveDataFrameColumn<DateTime>("Date", 
    Enumerable.Range(0, numDataPoint)
          .Select(offset => start.AddDays(offset))
          .ToList())
PrimitiveDataFrameColumn<int> data = new PrimitiveDataFrameColumn<int>("Data",
    Enumerable.Range(0, numDataPoint)
                        .Select(r => rand.Next(100))
                        .ToList())
var df = new DataFrame(date, data);
df

CSV文件

DataFrame也可以通过调用DataFrame.LoadCsv静态方法从CSV文件创建。

下面的代码从ohcldata.csv文件创建一个DataFrame。这个文件是从5.30下载的。例子--基本OHLC(开盘价、最高价、最低价、收盘价)金融图谱网站。这个文件包含每天的开盘价、最高价、最低价、收盘价的金融数据。

var df1 = DataFrame.LoadCsv("ohlcdata.csv");
df1

然后,可以使用Info方法来生成DataFrame中每一列的摘要。

访问DataFrame

通过索引访问数据

可以使用行索引和列索引来访问DataFrame中的具体数据。索引是基于零的编号。

下面的代码访问第一行和第二列的数据。

Out[7]: 11; First Row, Second Column

之后,可以给DataFrame分配一个新的值。

下面的代码将第一行和第二列的数据增加了10。

df[0,1] = int.Parse(df[0,1].ToString()) + 10;
df.Head(10)

Out[8]: Chart; Increase Data by 10

访问行数据

可以通过使用行索引来访问整个行。该索引是基于零的编号。

下面的代码访问了DataFrame中的第10行。

列索引也可以用来访问该行中的特定列。

下面的代码访问了第十行中的第四列。

然后,新的值也可以被分配给该列。

下面的代码将50000000分配给了第六列。

df1.Rows[9][5] = 50000000f;
df1.Head(10)

Out[11]: Chart; Access Row Data

访问列的数据

可以通过使用列名或索引来访问整个列。索引是基于零的编号。

下面的代码访问了DataFrame中名为Data的列(第二列)。

//df.Columns["Data"] or df.Columns[1]
df.Columns["Data"]  

Out[12]: Chart; Access Column Data

该列中的数据可以通过使用DataFrame的重载操作符来改变。

下面的代码将该列中的所有数据增加10。

 df.Columns["Data"]= df.Columns["Data"]+10;
 df

Out[13]: Chart; Increase All Data By 10

插入数据

添加一个新列

DataFrame在DataFrameColumnCollection中维护了一个DataFrameColumns的列表。可以向DataFrameColumnCollection添加一个新的列。

下面的代码向DataFrame添加了一个新的整数列。

df.Columns.Add(new PrimitiveDataFrameColumn<int>("Data1", df.Rows.Count()));
df

Out [14]: Chart; Add a New Column

新列中的数据被设置为null。

下面的代码将新列(Data1)中的空值填充为10。

df.Columns["Data1"].FillNulls(10, true);
df

Out[15]: Chart; Fill Null Values

附加一个新行

Append方法可以用来向DataFrame追加新行。

下面的代码创建了一个KeyValuePair实例的列表,然后将其添加到DataFrame中。

df.Append(new List<KeyValuePair<string, object>>() { 
    new KeyValuePair<string, object>("Date", DateTime.Now),
    new KeyValuePair<string, object>("Data", 12),
    new KeyValuePair<string, object>("Data1", 50)
}, true);
df.Tail(10)

Out [16]: Chart; Append a New Row

操纵数据框架

对数据框架进行排序

OrderByOrderByDescending方法可用于按指定列对DataFrame进行排序。

下面的代码按照名为Data的列对DataFrame进行排序。

Out [17]:Chart; Sort DataFrame

对DataFrame进行分组

GroupBy方法可用于按列中的唯一值对DataFrame的行进行分组。

下面的代码通过名为Data的列对DataFrame进行分组,然后计算每组中的值的数量。

var groupByData = df.GroupBy("Data");
groupByData.Count().OrderBy("Data")

Out[18]: Chart; Group the DataFrame

过滤DataFrame

过滤器方法可以用来通过行索引或布尔值来过滤DataFrame。

下面的代码通过返回那些在名为Data的列中的值大于50的行来过滤DataFrame。

df.Filter(df.Columns["Data"].ElementwiseGreaterThan(50))

Out[19]: Chart; Filter the DataFrame

合并DataFrame

Merge方法可以用来以数据库式的连接方式合并两个DataFrames。

下面的代码通过使用两个DataFrame中包含的Date列来连接两个DataFrame。首先,它将df1Date列中的数据类型从字符串类型转换为DataTime类型。然后,它调用Merge方法来连接DataFrames。

df1.Columns["Date"] = new PrimitiveDataFrameColumn<DateTime>("Date", 
    df1.Columns["Date"]
                .Cast<object>()
                .ToList()
                .Select(x => DateTime.ParseExact(x.ToString(), "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture))
                .Cast<DateTime>());               

df1.Merge<DateTime>(df, "Date", "Date")

Out[20]: Chart; Merge the DataFrame

通过使用XPlot.Ploty绘制图表

XPlot.Ploty是一个用于F#和.NET编程语言的跨平台数据可视化包。它的基础是Plotly,它是流行的JavaScript图表库。

下面的例子演示了如何使用XPlot.Ploty通过使用DataFrame中的数据来绘制图表。

线形图

下面的代码从DataFrame中的Open列绘制了一个线形图。

var chart1 = Chart.Plot(
    new Graph.Scatter
    {
        x = df1.Columns["Date"],
        y = df1.Columns["Open"],        
        mode = "lines+markers"
    }
);
var chart1_layout = new Layout.Layout{
    title="Open Price",
    xaxis =new Graph.Xaxis{
        title = "Date"
        },
    yaxis =new Graph.Yaxis{
    title = "Price (USD)"
        }           
    };
chart1.WithLayout(chart1_layout);
chart1

Line Chart Example

有多条线的折线图

下面的代码在一个折线图中绘制了开盘列和收盘列。

var chart2_list = new List<Graph.Scatter> 
{
    new Graph.Scatter
    {
         x = df1.Columns["Date"],
        y = df1.Columns["Open"],
        name="Open",
        mode = "lines"
    },
    new Graph.Scatter    
    {       
        x = df1.Columns["Date"],
        y = df1.Columns["Close"],
        name="Close",
        mode = "lines"
    }
    
};
 
var chart2 = Chart.Plot(
    chart2_list
);
 
var chart2_layout = new Layout.Layout{
    title="Open and Close Price",
    xaxis =new Graph.Xaxis{
        title = "Date"
        },
    yaxis =new Graph.Yaxis{
    title = "Price (USD)"
        }           
    };
chart2.WithLayout(chart2_layout);
chart2

Multiple Line Chart Example

柱状图

下面的代码从DataFrame中的Volume列绘制了一个条形图。

var chart3 = Chart.Plot(

    new Graph.Bar
    {
        x = df1.Columns["Date"],
        y = df1.Columns["Volume"],        
        marker = new Graph.Marker{color = "rgb(0, 0, 109)"}
    }
);
var chart3_layout = new Layout.Layout{
    title="Volume",
    xaxis =new Graph.Xaxis{
        title = "Date"
        },
    yaxis =new Graph.Yaxis{
    title = "Unit"
        }           
    };
chart3.WithLayout(chart3_layout);
chart3

Bar Chart Example

烛台图

下面的代码从DataFrame中的Open,High,Low,Close列绘制了一个蜡烛图。

var chart4 = Chart.Candlestick(df1.OrderBy("Date").Rows.Select(row => new Tuple<string, double, double, double, double>(
                 ((DateTime)row[0]).ToString("yyyy-MM-dd"),
                 double.Parse(row[1].ToString()),
                 double.Parse(row[2].ToString()),
                 double.Parse(row[3].ToString()),
                 double.Parse(row[4].ToString())
                )));
chart4.WithLayout(new Layout.Layout{
    title="OHLC",
    xaxis =new Graph.Xaxis{
        title = "Date"
        },
    yaxis =new Graph.Yaxis{
    title = "Price (USD)"
        }           
    });
chart4

Candlestick Chart