ML.NET Series: Perkenalan dengan DataFrame

Hi Makers,

Apa kabar ? Sudah lama tidak menulis sejak 2019, nah ini menjadi post pertama di tahun 2020. Pada seri kali ini kita akan mencoba DataFrame yaitu in-memory data structure, Data Frame dapat digunakan untuk memanipulasi data di jupyter notebook. Jika rekan-rekan belum pernah menggunakan .NET dengan jupyter notebook, silakan baca artikel pada link ini terlebih dahulu.

Mari kita mulai bereksperimen dengan DataFrame untuk memahaminya. Silakan buat notebook baru dengan kernel .NET (C#). Lalu kita tambahkan referensi ke nuget package sebagai berikut:

#r "nuget:Microsoft.Data.Analysis,0.2.0"
using Microsoft.Data.Analysis;
using System.Data;
using XPlot.Plotly;

DataFrame berada dalam package Microsoft.Data.Analysis, sedang package Xplot kita gunakan nanti untuk memvisualisasi data dalam bentuk chart.

Secara internal DataFrame menyimpan data dalam bentuk koleksi kolom, setiap kolom memiliki tipe data tertentu. 

Oke selanjutnya kita coba tambahkan beberapa kolom:

PrimitiveDataFrameColumn<DateTime> createdDate = new PrimitiveDataFrameColumn<DateTime>("CreatedDate");
PrimitiveDataFrameColumn<float> temp = new PrimitiveDataFrameColumn<float>("Temp");
PrimitiveDataFrameColumn<bool> status = new PrimitiveDataFrameColumn<bool>("Status",10);
StringDataFrameColumn deviceName = new StringDataFrameColumn("DeviceName",10);
StringDataFrameColumn actions = new StringDataFrameColumn("Actions", 10);
StringDataFrameColumn factory = new StringDataFrameColumn("Factory", 10);

Ada 2 tipe kolom disini, PrimitiveDataFrameColumn untuk menyimpan tipe data primitiv seperti int, float, decimal, bool, dsb. StringDataFrameColumn khusus untuk tipe data string. Setiap mendeklarasikan kolom kita perlu memasukan parameter Length yaitu jumlah datanya.  Kita coba dengan 10 data sebagai contoh. 

Lalu kita masukan dengan data sampel ke DataFrame. Berikut kodenya:

Random rnd = new Random(Environment.TickCount);
Enumerable.Range(1, 10).ToList().ForEach(x => { createdDate.Append(DateTime.Now.AddDays(x)); temp.Append(rnd.Next(25, 50)); deviceName[x-1] = $"device-{x}";factory[x - 1] = $"factory-{rnd.Next(1, 3)}"; });

var df = new DataFrame(createdDate, deviceName, factory, temp, status, actions);
df.Info()

Nah kita coba mengenumerasi data dari 1 – 10, dan memasukan data contoh berupa sensor temperatur pada suatu pabrik di hari yang berbeda. Perhatikan kita memasukan data pada koleksi kolom kita, lalu kita buat objek DataFrame dan memasukan kolom tersebut sesuai urutan. Untuk kolom actions karena tidak di isi maka defaultnya akan diisi dengan nilai null.

df.Info() digunakan untuk mengetahui informasi mengenai tipe data pada setiap kolom. DataFrame juga bisa memuat data dari file CSV secara langsung dengan method : DataFrame.LoadCsv(“sample.csv”);

Karena jupyter notebook bersifat interaktif (REPL), maka kita bisa langsung melihat isi variabel dengan mengetik nama variabelnya langsung seperti mengetikan df pada notebook. 

Tampilannya masih berbentuk koleksi kolom, tentunya ini sulit untuk dibaca oleh karena itu perlu ditambahkan formatter agar tampilan DataFrame lebih mudah dibaca dengan menampilkannya sebagai tabel. Masukan kode berikut:

using Microsoft.AspNetCore.Html;
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 = 20;
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);
}, "text/html");

Silakan ketik df dan eksekusi cell, maka tampilan sudah berbentuk tabel. Formatter diatas akan mencetak 20 baris pertama atau sesuai jumlah data jika data lebih sedikit.

DataFrame atau DataFrameColumn memiliki method-method (binary operation, computation, join, merge, handle missing value, dsb) yang dapat kita gunakan untuk memanipulasi data, berikut ini contohnya:

df["Temp"] = temp + rnd.Next(-1,1);

Jadi setiap cell dalam kolom “Temp” akan ditambahkan nilai random.

Kita juga bisa mengiterasi untuk mengubah kolom status berdasarkan nilai temp dengan threshold tertentu:

for(int row = 0; row < temp.Length; row++)
{
status
= temp
<= 30;
}
df

Nah sekarang semua kolom status yang bernilai false kita kasih actions “device perlu di reset” :

for (int row = 0; row < status.Length; row++)
{
if (!status
.Value)
df
= "device perlu di reset";

}
df

Nah, sedangkan yang statusnya “True” nilai kolom Actionsnya masih null. Kita bisa replace semua kolom null dengan nilai lain dengan kode berikut:

df["Actions"].FillNulls("-", inPlace: true);
df

Kita dapat pula mengiterasi semua data dalam bentuk baris dan kolom pada DataFrame, sebagai berikut saya contohkan bagaimana mengonversi DataFrame menjadi tipe DataTable sebagai berikut:

DataTable dt = new DataTable("data sensor");

foreach (var dc in df.Columns)
{
dt.Columns.Add(dc.Name.Replace(" ", "").Trim());
}
dt.AcceptChanges();

for (long i = 0; i < df.Rows.Count; i++)
{
DataFrameRow row = df.Rows[i];
var newRow = dt.NewRow();
var cols = 0;
foreach (var cell in row)
{
newRow[cols] = cell.ToString();
cols++;
}
dt.Rows.Add(newRow);
}
dt.AcceptChanges();

Object dt berisi data yang sama dengan DataFrame. Untuk membuktikannya mari kita buat Formatter untuk memvisualisasi object DataTable:

Formatter<DataTable>.Register((df, writer) =>
{
var headers = new List<IHtmlContent>();
headers.Add(th(i("index")));
foreach (DataColumn dc in df.Columns)
{
headers.Add((IHtmlContent)th(dc.ColumnName));
}

var rows = new List<List<IHtmlContent>>();
var take = 20;
for (var i = 0; i < Math.Min(take, df.Rows.Count); i++)
{
var cells = new List<IHtmlContent>();
cells.Add(td(i));
DataRow obj = df.Rows[i];

for (int x = 0; x < df.Columns.Count;x++)
{
cells.Add(td(obj[x].ToString()));
}


rows.Add(cells);
}

var t = table(
thead(
headers),
tbody(
rows.Select(
r => tr(r))));

writer.Write(t);
}, "text/html");

Lalu ketik dt untuk melihat isi dari data table. Kita dapat melakukan filtering dengan cara berikut:

PrimitiveDataFrameColumn<bool> boolFilter = df["Actions"].ElementwiseNotEquals("-");
DataFrame filtered = df.Filter(boolFilter);
filtered

Kita filter semua data yang tidak ada actionsnya. Kita bisa juga lakukan sorting data berdasarkan kolom tertentu dengan cara berikut:

DataFrame sorted = df.Sort("Temp");
sorted

Data tersortir berdasarkan nilai temp dari rendah ke besar. Lalu kita bisa juga lakukan aggregasi data dengan GroupBy lalu menghitung nilai rata-rata temperatur. seperti berikut:

GroupBy groupBy = df.GroupBy("Factory");

DataFrame groupCounts = groupBy.Count();
DataFrame tempGroupAvg = groupBy.Mean("Temp");
tempGroupAvg

Dan data di DataFrame dapat divisualisasikan dalam chart dengan XPlot, sebagai contoh kita akan menggambar line chart nilai temperatur per-harinya:

var lineChart = Chart.Line(df.Rows.Select(g => new Tuple<DateTime, float>(Convert.ToDateTime(g[0]), Convert.ToSingle(g[3]))));
lineChart.WithTitle("Temperature per Date");
display(lineChart);

Masih banyak fungsi-fungsi di dalam DataFrame yang belum dijelaskan disini seperti Append, Join, Merge nah ini PR rekan-rekan untuk explorasi. 

Rekan-rekan bisa juga menggunakan LINQ untuk melakukan query karena DataFrameColumn mengimplementasi IEnumerable<T>. DataFrame ini dapat digunakan bersama ML.NET dan .NET for Spark.  

Source code dapat rekan-rekan tarik dari sini.

Selamat berkarya, Salam Makers ;D

Loading

You May Also Like