Skip to content

Commit adb8bbe

Browse files
Initial commit. Lots to do.
1 parent 11f72b2 commit adb8bbe

18 files changed

Lines changed: 1192 additions & 0 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bin/
2+
obj/
3+
.vs/
4+
*Technical*

ClashBehaviour.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Dimension.DataFrame.Extensions;
2+
3+
public enum ClashBehaviour
4+
{
5+
KeepOriginal,
6+
ReplaceOriginal,
7+
Exception
8+
}

DataFrameExtensions.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Linq;
3+
using System.Numerics;
4+
using Microsoft.Data.Analysis;
5+
6+
namespace Dimension.DataFrame.Extensions;
7+
8+
/// <summary>
9+
/// Methods for adding calculations extension methods to make Microsoft's DataFrame a little more user-friendly.
10+
/// </summary>
11+
public static class DataFrameExtensionsCalculations
12+
{
13+
public static PrimitiveDataFrameColumn<T>? Diff<T>(this DataFrameColumn? column, string name = "", T? seed = default)
14+
where T : unmanaged, INumber<T>
15+
{
16+
if (column is null)
17+
{
18+
return null;
19+
}
20+
21+
var newName = string.IsNullOrEmpty(name) ? column.Name + "_Diff" : name;
22+
var newColumn = new PrimitiveDataFrameColumn<T>(newName, Enumerable.Repeat(seed, (int) column.Length));
23+
for (var i = 1; i < column.Length; i++)
24+
{
25+
newColumn[i] = (dynamic) column[i] - (dynamic) column[i - 1];
26+
}
27+
28+
return newColumn;
29+
}
30+
31+
public static PrimitiveDataFrameColumn<T> Apply<T>(this PrimitiveDataFrameColumn<T> column, Func<T, T> operation, string name = "")
32+
where T : unmanaged, INumber<T>
33+
{
34+
if (string.IsNullOrEmpty(name))
35+
{
36+
name = string.IsNullOrEmpty(name) ? column.Name + "_Applied" : name;
37+
}
38+
39+
var newColumn = new PrimitiveDataFrameColumn<T>(name, column.Length);
40+
for (var i = 0; i < column.Length; i++)
41+
{
42+
var rawValue = column[i];
43+
if (rawValue != null)
44+
{
45+
var castedValue = (T) rawValue;
46+
newColumn[i] = operation(castedValue);
47+
}
48+
else
49+
{
50+
newColumn[i] = null;
51+
}
52+
}
53+
54+
return newColumn;
55+
}
56+
57+
public static PrimitiveDataFrameColumn<T> Pow<T>(this PrimitiveDataFrameColumn<T> column, double power, string name = "")
58+
where T : unmanaged, INumber<T>
59+
{
60+
var result = new T[column.Length];
61+
for (var i = 0; i < column.Length; i++)
62+
{
63+
var value = column[i];
64+
if (value.HasValue)
65+
{
66+
// Convert to double, apply power, and then try to convert back to T.
67+
// This approach has limitations and might not work for all INumber<T> types, especially those that cannot be accurately represented as double.
68+
var poweredValue = Math.Pow(Convert.ToDouble(value.GetValueOrDefault()), power);
69+
result[i] = T.CreateChecked(poweredValue);
70+
}
71+
else
72+
{
73+
// Use default(T) to represent missing values, as NaN is not universally applicable.
74+
result[i] = default;
75+
}
76+
}
77+
78+
if (string.IsNullOrEmpty(name))
79+
{
80+
name = $"{column.Name}_Pow{power}";
81+
}
82+
83+
return new PrimitiveDataFrameColumn<T>(name, result);
84+
}
85+
}

DataFrameExtensionsArithmetic.cs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.Linq;
3+
using System.Numerics;
4+
using Microsoft.Data.Analysis;
5+
6+
namespace Dimension.DataFrame.Extensions;
7+
8+
/// <summary>
9+
/// Arithmetic extension methods to make Microsoft's DataFrame a little more user-friendly.
10+
/// </summary>
11+
public static class DataFrameExtensionsArithmetic
12+
{
13+
public static PrimitiveDataFrameColumn<T> Plus<T>(this PrimitiveDataFrameColumn<T> column, PrimitiveDataFrameColumn<T> otherColumn, string name = "")
14+
where T : unmanaged, INumber<T>
15+
{
16+
return column.Plus<T>(name, otherColumn);
17+
}
18+
19+
public static PrimitiveDataFrameColumn<T> Plus<T>(this PrimitiveDataFrameColumn<T> column, string name = "", params PrimitiveDataFrameColumn<T>[] otherColumns)
20+
where T : unmanaged, INumber<T>
21+
{
22+
if (otherColumns.Any(c => c.Length != column.Length))
23+
{
24+
throw new ArgumentException("All columns must have the same length.");
25+
}
26+
27+
var result = new T[column.Length];
28+
for (var i = 0; i < column.Length; i++)
29+
{
30+
var sum = column[i] ?? default;
31+
foreach (var otherColumn in otherColumns)
32+
{
33+
sum += otherColumn[i] ?? default;
34+
}
35+
36+
result[i] = sum;
37+
}
38+
39+
if (string.IsNullOrEmpty(name))
40+
{
41+
var namesToConcat = new[] {column.Name}.Concat(otherColumns.Select(c => c.Name));
42+
name = string.Join("+", namesToConcat);
43+
}
44+
45+
return new PrimitiveDataFrameColumn<T>(name, result);
46+
}
47+
48+
public static PrimitiveDataFrameColumn<T> Minus<T>(this PrimitiveDataFrameColumn<T> column, PrimitiveDataFrameColumn<T> columnToSubtract, string name = "")
49+
where T : unmanaged, INumber<T>
50+
{
51+
if (column.Length != columnToSubtract.Length)
52+
{
53+
throw new ArgumentException("Both columns must have the same length.");
54+
}
55+
56+
var result = new T[column.Length];
57+
for (var i = 0; i < column.Length; i++)
58+
{
59+
var value1 = column[i] ?? default; // Handle nulls as default(T)
60+
var value2 = columnToSubtract[i] ?? default; // Handle nulls as default(T)
61+
result[i] = value1 - value2; // Correct use of subtraction with INumber<T>
62+
}
63+
64+
if (string.IsNullOrEmpty(name))
65+
{
66+
name = $"{column.Name}_Minus_{columnToSubtract.Name}";
67+
}
68+
69+
return new PrimitiveDataFrameColumn<T>(name, result);
70+
}
71+
72+
public static PrimitiveDataFrameColumn<T> Times<T>(this PrimitiveDataFrameColumn<T> column, PrimitiveDataFrameColumn<T> columnToMultiplyBy, string name = "")
73+
where T : unmanaged, INumber<T>
74+
{
75+
// Wrapper around the Times method with params
76+
return Times(column, name, columnToMultiplyBy);
77+
}
78+
79+
// Times method for a params array of columns
80+
public static PrimitiveDataFrameColumn<T> Times<T>(this PrimitiveDataFrameColumn<T> column, string name = "", params PrimitiveDataFrameColumn<T>[] otherColumns)
81+
where T : unmanaged, INumber<T>
82+
{
83+
if (otherColumns.Any(c => c.Length != column.Length))
84+
{
85+
throw new ArgumentException("All columns must have the same length.");
86+
}
87+
88+
var result = new T[column.Length];
89+
for (var i = 0; i < column.Length; i++)
90+
{
91+
var product = column[i] ?? default;
92+
foreach (var otherColumn in otherColumns)
93+
{
94+
product *= otherColumn[i] ?? default;
95+
}
96+
97+
result[i] = product;
98+
}
99+
100+
if (string.IsNullOrEmpty(name))
101+
{
102+
var namesToConcat = new[] {column.Name}.Concat(otherColumns.Select(c => c.Name));
103+
name = $"{column.Name}_Times_{string.Join("_", namesToConcat)}";
104+
}
105+
106+
return new PrimitiveDataFrameColumn<T>(name, result);
107+
}
108+
109+
public static PrimitiveDataFrameColumn<T> Divide<T>(this PrimitiveDataFrameColumn<T> numeratorColumn, PrimitiveDataFrameColumn<T> divisorColumn, string name)
110+
where T : unmanaged, INumber<T>
111+
{
112+
if (numeratorColumn.Length != divisorColumn.Length)
113+
{
114+
throw new ArgumentException("Both columns must have the same length.");
115+
}
116+
117+
var result = new T[numeratorColumn.Length];
118+
for (var i = 0; i < numeratorColumn.Length; i++)
119+
{
120+
var numerator = numeratorColumn[i] ?? default; // Handle nulls as default(T)
121+
var divisor = divisorColumn[i] ?? default; // Handle nulls as default(T)
122+
123+
if (divisor == T.Zero || divisor == default) // Check for division by zero
124+
{
125+
result[i] = typeof(T) == typeof(float) ? (T) (object) float.NaN :
126+
typeof(T) == typeof(double) ? (T) (object) double.NaN :
127+
default;
128+
}
129+
else
130+
{
131+
result[i] = numerator / divisor; // Perform division
132+
}
133+
}
134+
135+
if (string.IsNullOrEmpty(name))
136+
{
137+
name = $"{numeratorColumn.Name}/{divisorColumn.Name}";
138+
}
139+
140+
return new PrimitiveDataFrameColumn<T>(name, result);
141+
}
142+
}

DataFrameExtensionsColumns.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Linq;
3+
using Microsoft.Data.Analysis;
4+
5+
namespace Dimension.DataFrame.Extensions;
6+
7+
/// <summary>
8+
/// Column extension methods to make Microsoft's DataFrame a little more user-friendly.
9+
/// </summary>
10+
public static class DataFrameExtensionsColumns
11+
{
12+
public static Microsoft.Data.Analysis.DataFrame SelectColumns(this Microsoft.Data.Analysis.DataFrame dataframe, params string[] columnNames)
13+
{
14+
var selectedColumns = dataframe.Columns
15+
.Where(c => columnNames.Contains(c.Name))
16+
.ToList();
17+
18+
if (selectedColumns.Count != columnNames.Length)
19+
{
20+
throw new ArgumentException("One or more column names do not exist in the DataFrame.");
21+
}
22+
23+
return new Microsoft.Data.Analysis.DataFrame(selectedColumns);
24+
}
25+
26+
public static bool ColumnExists(this Microsoft.Data.Analysis.DataFrame dataFrame, string columnName)
27+
{
28+
return dataFrame.Columns.Any(column => column.Name == columnName);
29+
}
30+
31+
public static bool TryGetColumn<T>(this Microsoft.Data.Analysis.DataFrame dataFrame, string columnName, out PrimitiveDataFrameColumn<T>? column)
32+
where T : unmanaged
33+
{
34+
column = null;
35+
var untypedColumn = dataFrame.Columns.FirstOrDefault(col => col.Name == columnName);
36+
if (untypedColumn is PrimitiveDataFrameColumn<T> typedColumn)
37+
{
38+
column = typedColumn;
39+
return true;
40+
}
41+
42+
return false;
43+
}
44+
}

DataFrameExtensionsCumulations.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Numerics;
2+
using Microsoft.Data.Analysis;
3+
4+
namespace Dimension.DataFrame.Extensions;
5+
6+
/// <summary>
7+
/// Cumulative extension methods to make Microsoft's DataFrame a little more user-friendly.
8+
/// </summary>
9+
public static class DataFrameExtensionsCumulations
10+
{
11+
public static PrimitiveDataFrameColumn<T> Cumulate<T>(this PrimitiveDataFrameColumn<T>? column, string newName = "", bool useNaN = false)
12+
where T : unmanaged, INumber<T>
13+
{
14+
var newColumnName = string.IsNullOrEmpty(newName) ? column.Name + "_Cumulative" : newName;
15+
var newColumn = new PrimitiveDataFrameColumn<T>(newColumnName, new T[column.Length]);
16+
T? sum = T.Zero;
17+
for (var i = 0; i < column.Length; i++)
18+
{
19+
var value = column[i];
20+
if (value.HasValue && sum.HasValue)
21+
{
22+
sum += value.Value;
23+
newColumn[i] = sum;
24+
}
25+
else
26+
{
27+
newColumn[i] = useNaN ? CreateNaN<T>() : default;
28+
}
29+
}
30+
31+
return newColumn;
32+
}
33+
34+
public static PrimitiveDataFrameColumn<T> CumulateAbs<T>(this PrimitiveDataFrameColumn<T> column, string newName = "", bool useNaN = false)
35+
where T : unmanaged, INumber<T>
36+
{
37+
if (string.IsNullOrEmpty(newName))
38+
{
39+
newName = string.IsNullOrEmpty(newName) ? column.Name + "_Abs" : newName;
40+
}
41+
42+
var newColumn = new PrimitiveDataFrameColumn<T>(newName, new T[column.Length]);
43+
T? sum = T.Zero;
44+
for (var i = 0; i < column.Length; i++)
45+
{
46+
var value = column[i];
47+
if (value.HasValue && sum.HasValue)
48+
{
49+
var absValue = T.Abs(value.Value);
50+
sum += absValue;
51+
newColumn[i] = sum;
52+
}
53+
else
54+
{
55+
newColumn[i] = useNaN ? CreateNaN<T>() : default;
56+
}
57+
}
58+
59+
return newColumn;
60+
}
61+
62+
// Utility method to generate NaN for supported types, default otherwise
63+
private static T CreateNaN<T>()
64+
where T : unmanaged, INumber<T>
65+
{
66+
if (typeof(T) == typeof(float))
67+
{
68+
return (T) (object) float.NaN;
69+
}
70+
else if (typeof(T) == typeof(double))
71+
{
72+
return (T) (object) double.NaN;
73+
}
74+
// Extend to other types if they support NaN
75+
else
76+
{
77+
return default; // Fallback for types that do not support NaN
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)