跳到主要內容

Rust - 以 JSON 為資料來源建立 Polars DataFrame

緣起

在科技的蓬勃發展中,資料處理和分析變得愈發重要。Rust 語言的 Polars 框架為我們提供了一個強大的工具,讓資料操作變得更加容易且高效。這個框架支援以 JSON 格式匯入資料並建立 Dataframe,為我們提供了一個直觀且方便的方式來操作資料。

本文開始

本文的目的,為使用 JSON 為資料來源,在 Rust 中建立 Polars 的 Dataframe。在以下的文章裡,我們將在 Cargo.toml 中,使用如下的設定:

[dependencies] polars = { version = "0.36.2", features = ["json"] }

撰文的當下,Polars 的最新版本為 0.36.2。參考 Polars 的官方文件,它提供了以下的程式碼段落,以匯入 JSON 檔案內容:

use polars::prelude::*;

let mut file = std::fs::File::open("docs/data/path.json").unwrap();
let df = JsonReader::new(&mut file).finish().unwrap();

到這裡,就能輕鬆的利用一個 JSON 檔案的內容,建立出 Dataframe 物件。

好了,故事結束,收工。
.
.
.
.
.
如果故事真的就這麼簡單,我想也就沒有撰寫這篇文章的必要了。

想想上面程式段落的例子,過程裡需要透過一個存在於檔案系統中的 JSON 檔案為媒介,儲存我們要處理的資料,再透過「JsonReader」讀入的 Dataframe 中處理。如果這些資料是從網路上取得(例如呼叫RESTful API),以字串變數的形式存在於系統之中,例如下面的內容:

let msg = r#"[
    {"id": 1, "name": "A", "age": 10},
    {"id": 2, "name": "B", "age": 20}
]"#;

面對這樣的情境,若將資料先落地後再重新讀入,實在是有些不切實際,還可能因為受到磁碟 I/O 的效能影響,而拖慢了程式執行的效率。

那為了避免上述的問題,我們應該怎麼處理呢?

使用「Serde」框架,手動建立 Dataframe

在 Rust 語言的生態系中,有一個「Serde」框架可以協助我們處理 JSON 格式的訊息或檔案。為了使用「Serde」,我們需要調整「Cargo.toml」的內容如下:

[dependencies] polars = { version = "0.36.2", features = ["json"] }
serde = { version = "1", features = ["serde_derive", "derive"] }
serde_json = "1"

但在開始程式碼內容前,我們先說說整個方法的處理邏輯如下:

  1. 定義訊息格式;
  2. 把訊息的內容,透過「Serde」框架轉換為物件或清單(這個視訊息的內容,是否有陣列存在);
  3. 建立 Polars Dataframe。

定義訊息格式

這個動作裡,需要先定義好一個結構(struct);參考了 JSON 訊息的內容,我們將「User」這個結構定義如下:

use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct User {
    id: i32,
    name: String,
    age: i32,
}

這樣做的好處是,在匯入前就明確定義要處理的資料欄位、名稱,以及資料型態。我們會利用這個結構,來匯入 JSON 訊息。

匯入 JSON 訊息

這個步驟裡,我們會使用到「serde_json::from_str()」來匯入 JSON 訊息,具體的方式如下:

// Parse JSON string into a vector of Item structs
let users: Vec<User> = serde_json::from_str(msg).unwrap();

由於 JSON 訊息裡記載的是一個具有陣列型態的資料內容,因此在處理上,就應以「Vec<User>」來宣告,以同時儲存多個不同的「User」。

建立 Polars Dataframe

最後的重頭戲來了!!我們的目標是建立出 Polars Dataframe 物件,以進行後續資料的處理與操作。參考以下的程式片段,以完成這個作業:

use polars::prelude::*;

// Create a DataFrame from the vector of structs
let df = DataFrame::new(vec![
    Series::new("id", users.iter().map(|i| i.id).collect::<Vec<_>>()),
    Series::new("name", users.iter().map(|i| i.name.clone()).collect::<Vec<_>>()),
    Series::new("age", users.iter().map(|i| i.age).collect::<Vec<_>>())
    ]).unwrap();

// Display the DataFrame
println!("{:?}", df);

這裡使用的方式,是將「users」裡的各個欄位透過「iterator」的方式「抽出」,產生對應的資料序列(Series)後,再逐一填寫到 Dataframe 中,最終產生我們的 Dataframe - 「df」。

除了手動建立 Dataframe 的方式外,有沒有什麼比較好的方式,可以用來建立 Dataframe 呢?

透過「Cursor」建立字元串流

其實在 Rust 語言的「std::io」標準庫裡,有一個「Cursor」的 Wrapper 結構,它可以把一個字串轉換為資料串流,以進行 I/O 讀取操作;這個用法有點像我們在 Java 語言裡,會把「String」物件轉換為「StringReader」,再逐字或逐行的讀入這個字串內容。

相關的做法如下:

use polars::prelude::*;

// Create a Cursor from the input string
let cur = Cursor::new(msg);
let df = JsonReader::new(cur).finish().unwrap();

這個方法只用了短短的兩行,就完成了上述的 Dataframe 生成作業,是不是很方便?

結論

在本文中,我們探討了使用 Rust 語言的 Polars 框架處理 JSON 訊息的方法。從檔案匯入的方式進一步擴展,介紹了在處理來自網路的 JSON 字串時,如何避免因資料落地後再讀入的效能問題。透過 Serde 框架,我們定義了資料結構,並展示了如何手動建立 Dataframe,以及透過 std::io::Cursor 結構建立字元串流的方式,使產生 Dataframe 的作業更為簡單方便。這樣的處理方式不僅提高了效能,也讓資料處理更具彈性。

我尚在浩瀚的 Rust 世界中探索各種發展的可能。文章的內容皆為我實際操作後所撰寫下的過程及心得,雖已儘可能追求正確與嚴謹,但內容仍難免因觀念或理解而有所差誤,還望看到此文的同好先進,不忘指點改正,謝謝您。

最後附上本篇文章所用到的完整程式碼供大家參考:

use std::io::Cursor;

use polars::prelude::*;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct User {
    id: i32,
    name: String,
    age: i32,
}

fn main() {
    // Define your JSON message as a string
    let msg = r#"[
        {"id": 1, "name": "A", "age": 10},
        {"id": 2, "name": "B", "age": 20}
    ]"#;

    // Parse JSON string into a vector of Item structs
    let users: Vec<User> = serde_json::from_str(msg).unwrap();

    // Create a DataFrame from the vector of structs
    let df = DataFrame::new(vec![
        Series::new("id", users.iter().map(|i| i.id).collect::<Vec<_>>()),
        Series::new("name", users.iter().map(|i| i.name.clone()).collect::<Vec<_>>()),
        Series::new("age", users.iter().map(|i| i.age).collect::<Vec<_>>())
        ]).unwrap();

    // Display the DataFrame
    println!("{:?}", df);

   
    // Create a Cursor from the input string
    let cur = Cursor::new(msg);
    let df = JsonReader::new(cur).finish().unwrap();

    // Display the DataFrame
    println!("{:?}", df);

}


留言

這個網誌中的熱門文章

20070901 - 八斗子夜潛

其實這篇應該很早就得貼出來了,可是這陣子實在忙的很,每天都得上課到晚才能回家。整理完了當天上課的內容,也差不多該睡了,實在沒有多餘的時間可以整理照片。 昨天利用了一點時間,加了些夜班,終於把它們整理完,貼出來和大家一塊分享。 #1. 剛從雲中升起的旭日,映照在平靜的海面上; #2. 海底花園裡的海扇,跟著水流的節奏,隨波飄逸; #3. 辛勤的海葵蝦,正忙碌的整理著海葵構起的家; #4. #5. #6. #7. 中國管口魚(好長的嘴巴); #8. 別太靠近我,我可是盯著你看喔~; #9. 熱鬧的珊瑚礁,就像繁忙的台北街頭; #10. 唔~躲起來,你看不到我! #11. 不一樣的海蛞蝓 #12. 成群結隊的甲香魚,就像動作伶俐的小精靈; #13. 獅子魚是花園的常客; #14. 只要有來,就能遇見牠; #15. 冒了被咬的危險,好不容易拍到的小蝦子; #16. 哇唔~你猜我在哪裡?

帛琉三部曲(下集)

-- 寫在前頭 -- 看過幾個人所寫的遊記之後,我發現我寫的最多,把所有的鎖事都寫進來了…。But, so what? 馬可波羅遊記裡,不也都寫了些吃喝拉撒睡的雜事?! 只是每當想到還有好幾篇沒寫,懶蟲就又開始在做怪…。 -- May 19th, 2006, 晴 -- 結束了今天的海釣行程,我們轉往了長沙灘。長沙灘是個很特別的地方,當海水退潮時,會在兩座小島之間,連接著一條很長的白色沙灘,非常的漂亮。聽 Tim 跟小曼說,之前的長沙灘比現在還要更漂亮,由為颱風的關係,沙灘被風浪給破壞掉了,所以變成月彎型,而且沙灘也縮小了。雖然如此,我覺得長沙灘還是很美。 隨著時間的流逝,轉眼到了日落時分。我抬起頭來,望了望遠方的天空,等待著另一個神蹟的發生。晴朗而蔚藍的天空,隨著時間而改變,由藍變黃,再由黃而轉紅。我必須承認,這個景象是我前所未見的。它的美讓我感到辭窮,一時找不到任何辭彙來形容它。我曾一度懷疑我的眼睛,懷疑這令人感動的景色,就像是文藝復興時期大師巧手中渲染出的畫布;它是那麼的柔和、那麼的協調、那麼的讓人連聲嘆息,深怕此生再也見不到這番景色。 今天晚上我們留在長沙灘的這個小島上用晚餐,而晚餐的主菜,自然是下午的那些戰利品。這些現釣的新鮮海鮮果真是與眾不同,雖然只是火烤,肉質不但鮮嫩而且多汁。花花釣到的天狗鯛肉質不錯,可是吃起來有點腥;而我釣到的那條魚,吃起來就覺得肉質比較粗,但還蠻有嚼勁就是了。我喜歡吃這些魚,因為實在是太好吃了,而且沒有什麼魚刺,我討厭魚刺太多的魚…。 用完了晚餐,簡單的收拾過後,我們走到沙灘上,仰望著滿天星空。好美,我看到了北斗七星,看到了銀河,看到了南十字星,更看到了瞬間即逝的流星。我給自己許了願,至於是什麼願望,就不討論了。聽到阿勇細數著天上的星辰,心中不由得羨慕起他來,羨慕他能夠從小在這片人間樂土上長大。 Ivy 跟阿勇從箱子裡頭搬來了一些零食及昨晚沒喝完的半瓶威士忌。很顯然的,我們帶來的那些酒是不夠我們喝的(總共兩瓶威士忌、八瓶紅酒,不過這八瓶紅酒的酒精度都不高,只有8%)。喝完了這半瓶,酒蟲兒們可是得斷糧好幾天呢! 找不到杯子,沒幾個人喝,怪!所以阿勇便提議要玩遊戲,要玩什麼呢?就玩虎克船長好了!輸的喝酒,三次就脫褲子裸奔…!Tim,我們真的不是有心要害你的,誰叫你人緣那麼好,那麼受小姐們的歡迎…。一次,喝!兩次,喝!三次,我想接下來的事...

聯合報招待所─南園(一)

06/02是攝影班的第三次外拍,一早八點就到學校集合。那些可愛的阿伯同學還是一如往常,早早就在遊覽車上等我們了。網路上看南園的相關介紹,就覺得那裡的景色很漂亮,蠻期待今天的外拍。 本來說要帶甜姐兒米粒一塊去的,誰知道這隻懶鬼竟然裝龜不來了,害的一位滿懷期待,想要好好幫米粒拍照的同學落了個空,難過死啦!米粒,妳要負責。 一到了南園就覺得美麗的風景映入眼簾;可是我們在十點多到達,天氣正好,陽光也大,結果怎麼拍都是天空跟景物反差太大,動不動就爆掉。而且天氣熱,又爬上爬下的,搞的我滿身汗;反正光線不滿意,天氣熱、人又懶,干脆到處晃晃準備吃午飯。 太陽一直都很大。本來吃過午飯後,打算找個樹蔭好好的給它睡個午覺,等光線柔合一點再動。誰知道大家幹勁十足,一直嚷嚷著要去拍照了!唉,年輕人還是不要太懶,只好硬著頭皮跟著走。忘了帶腳架,相機拿不穩,只好拍些光線亮的;可是光線亮的又容易爆 :P 天啊!這不是存心搞我嗎?還好將近三點的時候,光線慢慢的開始柔合了下來,也有一些斜光。由於天氣很好,加上濾鏡的關係,所以看起來天空特別的藍;不多說,趕快拿起就是一陣猛拍嚕! 這次拍的照片還不少,而且品質好壞差很多,挑這些照片真是一種考驗。所以我先把部份處理好的,先貼上來分享,剩下的再另外貼一篇嚕! Fig1. 參觀由此進... Fig2. 餐廳前花圃 Fig3. 老師講評:紅色磚牆的部份,有點過曝、細節不明顯。 Fig4. 南樓一角(老師,我還是覺得你那張拍的好...) 老師講評:屋簷再突出一些到天空,整個構的的感覺會更好。 Fig5. Fig6. 這個景我看好多人也有拍耶... Fig7. 晴空下的沙漠玫瑰 Fig8. Fig9. 老師講評:如果可以把紅面的磚牆保留一些下來,整體的感覺會更棒。