3

我有各种时间序列,我想关联并呈现为 csv 文件或内存数据表 (.NET)。这些时间序列是时间-值对的数组(实际上这些对象不仅仅包含时间和值)。时间序列可能跨越不同的重叠时期,有些甚至可能有漏洞(给定时间戳的缺失值)。

对于那些感兴趣的人,我正在使用 OPC HDA .NET 库从 OPC HDA 服务器中提取历史时间序列。

生成的数据表应具有每个时间序列的一列,所有时间序列均基于时间戳列按时间顺序排列。请参见下面的示例:

|-------|-------|-------|-------|-------|
   TIME    TS1     TS2     TS3     TS4
|-------|-------|-------|-------|-------|
    1       X               X       X
|-------|-------|-------|-------|-------|
    2       X       X       X       X
|-------|-------|-------|-------|-------|
    3       X       X               X
|-------|-------|-------|-------|-------|
    4       X       X       X 
|-------|-------|-------|-------|-------|
    5       X       X       X 
|-------|-------|-------|-------|-------|

实现这一目标的最有效方法是什么?“有效”是指使用最少的代码。但是考虑到时间序列可能会变得非常大,内存使用也可能是一个问题。

4

4 回答 4

4

您可以首先扫描所有现有系列的不同值(例如,将它们聚合到 HashSet 中),然后简单地将它们转储到日期数组中(将日期和索引位置之间的匹配存储在字典中)。

var distinctDates = allSeries
  .SelectMany(s => s.Values.Select(v => v.Date))
  .Distinct()
  .OrderBy(d => d)
  .ToArray();

var datePositions = distinctDates
  .Select((d,index) => new 
    {
      Date = d,
      Index = index
    }).
  .ToDictionary(x => x.Date, x => x.Index);

然后,创建一个锯齿状数组,其宽度为“NumberOfSeries”,长度为“NumberOfDates”。之后,对所有数据进行第二次扫描并将它们转储到它们的位置。

var values = new float[allSeries.Length][];
for (var i=0;i<allSeries.Length;i++)
{
  values[i] = new float[distinctDates.Length];
  var currentSerie = allSeries[i];
  foreach(var value in currentSerie.Values)
  {
    var index = datePositions[value.Date];
    values[i][index] = value.Value;
  }      
}

我在没有接触 VisualStudio 的情况下编写了这段代码,所以我可能有一些拼写错误。或者可能使用了一些 .NET 中不存在的 LINQ 方法(只需查看Lokad.Shared.dll 即可)。但是你应该能够理解这个想法。

还有一些注意事项,当我谈到这个话题时:

  1. 如果您必须一次将所有内容保存在内存中,请选择锯齿状数组。它比字典效率更高,内存问题也比矩形数组少得多。

  2. 保持 Value 对象尽可能小(即:float 而不是 double)。

  3. 如果未来时间序列值的数量预计会变大,那么永远不要将数据库中的值存储在“每个值一行”中。建议要么使用HDF(具有 .NET 接口)之类的东西,要么在 DB 中使用二进制形式的持久时间序列片段(如时间序列数据库

坚持这些应该可以让你在没有很多问题的情况下扩展到数亿个时间值(做到了)。

于 2009-06-19T22:52:53.970 回答
2

您可能会使用嵌套字典之类的数据结构并迭代内容:

Dictionary <TimeSeries, Dictionary<DateTime, Value>> dict = new Dictionary<TimeSeries, Dictionary<DateTime, Value>>();

foreach (TimeSeries series in dict.Keys) {

    //table row output code goes here
    Dictionary<DateTime, Value> innerDict = dict[series];
    foreach (DateTime date in innerDict.Keys) {
        Value seriesValueAtTimeT = innerDict[date];
        //table column output code goes here
    }
}

根据您的需要,您的输出代码在哪里写入其他内容,并且您将数据类型 TimeSeries、Value 等替换为您的实际数据类型。

于 2009-06-19T20:40:49.273 回答
1

嘿,克里斯。我意识到您已经接受了答案,但我想我会发布我使用的解决方案。希望它对某人有用。如果不是,至少它为我在未来某个时候找到它提供了一个地方。:-)

这是直接从 Excel 2007 代码模块触发的 VBA 代码。它可以很容易地转换为.Net。

数据操作的关键是数据透视表对象。我发现它在将数据放入您指定的布局方面非常有效。

Sub GetIndexData ()
Dim cn as ADODB.Connection, cmd As ADODB.Command, rs As ADODB.Recordset
Dim rPivotTopLeft As Range, rPivotBottomRight As Range

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual

'Get the data.'
Set cn = New ADODB.Connection
With cn
  .Provider = "SQLOLEDB"
  .ConnectionString = "Database=" & mDBName & ";" & _
                      "Server=" & mDBServerName & ";" & _
                      "UID=" & mDBUserID & ";" & _
                      "Password=" & mDBPassword & ";" & _
                      "Persist Security Info=True;"
  .CursorLocation = adUseClient
  .Open
End With

Set cmd = New ADODB.Command
Set rs = New ADODB.Recordset
With cmd
  .ActiveConnection = adoTools.DBConnection
  .CommandType = adCmdText
  .CommandText = "SELECT YourData From YourSource WHERE YourCritera"
  Set rs = .Execute
End With



If Not (rs.BOF And rs.EOF) Then 'Check that we have some data.'

'Put the data into a worksheet.'
With wsRawData
  .Cells.CurrentRegion.Clear

  Set rPivotTopLeft = .Range("A1")
  With ThisWorkbook.PivotCaches.Add(SourceType:=xlExternal)
    Set .Recordset = rs
    .CreatePivotTable _
        TableDestination:=rPivotTopLeft, _
        TableName:="MyPivotTable"
  End With

  'Massage the data into the desired layout.'
  With .PivotTables("MyPivotTable")
    .ManualUpdate = True

    .PivotFields("Date").Orientation = xlRowField
    .PivotFields("Index").Orientation = xlColumnField
    .AddDataField .PivotFields("Return"), "Returns", xlSum

    .DisplayFieldCaptions = False
    .ColumnGrand = False
    .RowGrand = False

    .ManualUpdate = False
  End With

  mMonthCount = Range(.Range("A3"), .Cells(Rows.Count, "A").End(xlUp)).Count
  mIndexCount = Range(.Range("B2"), .Cells(2, Columns.Count).End(xlToLeft)).Count

  'Convert pivot table to values.'
  Set rPivotBottomRight = .Cells(mMonthCount + 2, mIndexCount + 1)
  With .Range(rPivotTopLeft, rPivotBottomRight)
    .Copy
    .PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
  End With

  'Format the worksheet.'
  .Range("A3").Resize(mMonthCount, 1).NumberFormat = "mmm-yy"
  .Range("B3").Resize(mMonthCount, mIndexCount).NumberFormat = "0.00%"
  Union(.Rows(2), .Columns(1)).Font.Bold = True
  .Cells.ColumnWidth = 7.14
  .Rows(1).Delete

End With


rs.close
Set rs = Nothing
cmd.ActiveConnection = Nothing
Set cmd = Nothing
cn.close
Set cn = Nothing

End Sub

从那里可以相对容易地利用内置的 excel 回归统计来输出相关矩阵。使用这种技术,我将在大约 45 秒内生成一个具有 600x600 相关矩阵的工作表。

请注意,应更改 .PivotFields 参数以适合数据源中数据的列名。

于 2009-06-24T03:40:47.043 回答
0

如果内存使用成为问题,您可以做的一件事是从跟踪单个事件转移到给定时间段内的事件列表计数。你会失去一些关于事情发生的确切时间的精确度,但经常像这样总结你的数据可以降低你的图片的复杂性并使趋势更加明显。

如果不是很明显,我的意思是获取如下所示的数据:

12:00 event1
12:01 event2
12:10 event1
12:11 event1

对此:

12:00-12:15 event1 3
12:00-12:15 event2 1
于 2009-06-19T21:04:59.680 回答