最近開始探索 Python 新大陸(延伸閱讀:寫 C# 已得心應手,為什麼我該學 Python?),雖然有 Github Copilot 助拳,卡在基本功不夠紮實,連一些基本的 Group By、排序、資料重新組裝都要耗上大半天。

細究原因,明明腦中一秒就想好用 .NET 要怎麼做,用文字跟 AI 解釋得講上一大串。說明不到位 AI 會錯意給錯 Python 寫法,自己又無力一眼看出問題,只能不斷試誤修正... 心中難免碎唸:幹! 拎杯用 LINQ 一分鐘就寫完的東西,居然搞了十幾分鐘還沒弄好!

靈光乍現,幹嘛花力氣想盡辦法用文字跟 AI 解釋?程式碼本身就是最完美的程式規格呀,直接給 LINQ 程式請 Copilot 翻成 Python 不就好了?

以常見 CSV 原始資料彙整分析為例。之前我多半會開個 C# 免洗專案,在其中讀檔用 LINQ 分群排序,必要時再搭配 Dictionary<T, T>/List<T>,邊寫邊改,改完再重跑。而這類摸索程式寫法的過程,我從 Python 的 Jupyter 筆記本學到另一種更方便即興式操作概念。

假設 CSV 資料要歷經分群、比對、匯出三個步驟,其中比對這部分比較複雜,有好幾種可能做法,要反覆嘗試。我可以先在一個程式區塊先分群好存成清單變數,在第二個程式區塊對該清單執行比對運算,若結果不理想,只需修改第二區塊的程式碼嘗試對同一清單變數進行運算,不像之前開 C# 免洗專案,改完程式要重頭開始跑,第一段分群做了又做。在需要進行各式即興操作,嘗試不同做法的情境下,Jupyter 筆記比開 Console 專案更方便。

花了點時間,我學會用 VSCode 開 Polyglot Notebook 讀 CSV 檔,用 LINQ 整理彙總資料的做法,比過去開免洗 Console 專案簡便有效率,可分段儲存執行結果,反覆嘗試而不用每次從頭跑。找出 LINQ 版資料處理方式後,若有需要改用 Python,將程式碼交給 Copilot 翻成 Python 版,通常能一次到位得到等效寫法,比起打字跟 AI 解釋半天有效率十倍。

我用 Kaggle 的 IMDB 前 1000 部電影資料集當對象練習,試著出作品最多的前 10 名導演,整理這 10 名導演的所有作品。

以上資料整理需求對 LINQ 是小菜一碟,用 VSCode 開個 Polyglot Notebook 邊寫邊試,程式碼可分段處理,在同一畫面反覆嘗試,比開專案測試簡便。

若要測 C# 程式,可選 Polyglot .dib 格式 (.dib 不支援 Python,但 VSCode 整合性比 .ipynb 好),一開始先 #r "nuget: CsvHelper" 引用 CsvHelper NuGet 套件,接著便可使用 CsvReader 讀入 CSV。建議將程式拆成多段,如下圖,第一段載入 NuGet、第二段讀入 CSV 並 GroupBy + OrderBy + Take 取前十名、第三段整理前 10 名導演作品、第四段將結果匯出成 CSV 檔。

分段的好處是前段處理完的結果會保存在記憶體,下一段可以直接取得該變數加工,若結果不如預期,可繼續對該變數做不同嘗試,不需重頭跑起。Polyglot Notebook 的這個特性在學習程式或嘗試新點子時格外好用。按一下 Variables [1]可以檢視目前筆記本使用中的變數,還可以跨程式語法共用[2]:(註:按下 Share 選 JavaScript 會新增 JavaScript 程式區塊,填入 #!set --value @csharp:top10Directors --name top10Directors 指令)

如下圖,可以將 C# 產生的變數交給 JavaScript 區塊接力處理:

這裡分享實戰小技巧,網路上看到的 CsvHelper 範例,多半依 CSV 欄位規格定義物件接收資料並支援自訂型別對映邏輯。但對於較簡單的 CSV 操作,其實可省去宣告強型別物件的工夫,比照 Dapper 用 CsvReader.GetRecords<dynamic>() 轉成動態物件就好。不過有個眉角,dynamic 型別在轉成 JSON 或匯出 CSV 時會發生無法識別屬性的問題,要解決不難,轉成匿名型別另外宣告名稱並強制轉換型別即可,例如: .Select(o => new { Director = (string)o.Director, Title = (string)o.Series_Title, Year = (string)o.Released_Year })

完整程式範例如下:

using CsvHelper;
using System.Globalization;
using System.IO;
// Columns: Poster_Link,Series_Title,Released_Year,Certificate,Runtime,Genre,IMDB_Rating,
// Overview,Meta_score,Director,Star1,Star2,Star3,Star4,No_of_Votes,Gross
var reader = new StreamReader("imdb_top_1000.csv");
var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
// 讀取 CSV 檔案,解析成動態物件
var records = csv.GetRecords<dynamic>().ToList();
// 取得作品數量前 10 的導演
var top10Directors = records.GroupBy(record => record.Director)
    .Where(group => group.Count() > 1)
    .OrderByDescending(group => group.Count())
    .Take(10)
    .Select(group => new { Director = group.Key, Count = group.Count() }).ToList();
    
Console.WriteLine("Top 10 Directors:");
var list = new List<dynamic>();
foreach (var director in top10Directors)
{
    // 顯示導演姓名與其作品數量
    Console.WriteLine($" * {director.Director} ({director.Count})");
    // 取得該導演的作品,並依照發行年份排序,列出導演、作品名稱與發行年份
    var movies = records.Where(o => o.Director == director.Director)
        .OrderBy(o => o.Released_Year)
        // 只取導演、作品名稱與發行年份,轉換成匿名型別需命名屬性並指定型別,後續寫入 CSV 檔案時才能正確轉換
        .Select(o => new { Director = (string)o.Director, 
                           Title = (string)o.Series_Title, Year = (string)o.Released_Year })
        .ToList();            
    list.AddRange(movies);
    // 顯示其作品
    list.Select(o => $"   - {o.Title} ({o.Year})").ToList().ForEach(Console.WriteLine);
}

// 將資料寫入 CSV 檔案
var writer = new StreamWriter("top10_director_movies.csv");
var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);
csvWriter.WriteRecords(list);
csvWriter.Dispose();

最後匯出前十名導演的作品共 101 部:(赫然發現,侏儸紀公園竟已是三十多年前的事惹...)

至此,我們學會不需開專案,分階段用 LINQ 與 .NET 程式庫對 CSV 進行即興資料整理的工作模式,擁有完整 Intellisense 及 Github Copilot 支援,又比開免洗 Console 專案方便,整理資料再多了好選擇。

Exploring Python with the help of Github Copilot, I leveraged C# LINQ for efficient data manipulation from CSV files. Using VSCode’s Polyglot Notebook, I streamlined the process of grouping, sorting, and transforming data. The best part is now I have a convenient way to convert the code to Python for equivalent functionality.


Comments

Be the first to post a comment

Post a comment