.NET + Python + JS в Jupyter? Ласкаво просимо до Polyglot Notebooks!

Ви колись уявляли, що займаєтесь наукою про дані у вашій улюбленій мові програмування? Або просто хотіли створити прототип вашого коду з належною візуальною підтримкою? Або навчати та документувати приклади коду? Або просто вивчати мови чи бібліотеки?

Виявляється, є інструмент для цієї роботи, який називається Jupyter Notebook. Однак довгий час його асоціювали тільки з Python, оскільки він був створений для цієї мови. Але тепер усе змінилося, оскільки Polyglot Notebook змінює ситуацію.

Polyglot Notebook дозволяє використовувати не лише Python, R, C#, F#, JS, SQL, KQL, HTML та інші мови, але й об'єднує їх під єдиним дахом!

Polyglot Notebook — це розширення для VS Code від команди .NET Interactive Microsoft, яке дозволяє використовувати найпопулярніші мови в Jupyter notebook.

Ось кілька основних особливостей.

  1. Підтримка багатьох мов/ядр:

Як вже згадувалося, ви можете використовувати кілька мов в одному Polyglot notebook. Але не тільки це, ви також можете підключатися до інших Python ядер через URL.
Це не тільки дозволяє використовувати єдиний редактор коду скрізь, але й об'єднує кілька ядер в одному notebook.

  1. Перехресне спілкування між мовами/комірками/ядрами:

Створюйте змінну в Python і одразу передавайте її в C#, і навпаки. Ця функція доступна для багатьох мов у notebook, таких як Python, C#, F#, JS, R, SQL та інші. Як розробник, це не тільки дозволяє вам використовувати правильну мову для правильної задачі, але й відкриває можливість досліджувати область науки про дані у вашій улюбленій мові програмування.

Наприклад: завантажте ваш CSV у JS, очистіть його в C#, візуалізуйте в R і тренуйте модель AI у Python, все це в одному Jupyter notebook з чудовим користувацьким досвідом.

  1. Jupyter для мов, окрім Python:

Використовуючи Polyglot, тепер ви можете створювати notebook на JS, C#, F# тощо. Легкі приклади коду та документація. Повний досвід Jupyter, адаптований до вашої бажаної мови — це справжній прорив.
Це не тільки дозволяє спільноті писати notebook на їхній мові, але й відкриває двері для нових розробників, щоб вони могли використовувати ці мови в галузі AI без обмежень інструментів. Іншими словами, тепер для кожної підтримуваної мови є рівні умови для AI/науки про дані.

  1. Використовує файл .ipynb

Іншими словами, це Jupyter notebook, а не якийсь інший формат файлу. Поділіться ним, переглядайте і виконуйте комірки.

  1. Інтерактивні візуалізації

З C#, JS або Mermaid можна створювати інтерактивні візуалізації. Ми також розглянемо це на демонстрації.

Як почати:

Необхідні умови:

  • Остання стабільна версія dotnet sdk (на даний момент .NET 7)
  • Для використання Python потрібно встановити Python і jupyter notebook (модуль "jupyterlab").

Перейдіть до VS Code і встановіть розширення Polyglot Notebook.

Створіть перший notebook, створивши файл .ipynb і вибравши “.NET Interactive” як ядро.
Напишіть код у комірці, змініть ядро та поділіться змінними між різними ядрами.

Щоб підключитися до Python, дотримуйтесь інструкцій за цим посиланням

interactive/docs/jupyter-in-polyglot-notebooks.md at main · dotnet/interactive · GitHub

Демо Notebook:

Для цього демонстраційного прикладу я буду використовувати тільки C# і Python. Але ви можете використовувати JS та інші мови за потребою.
Весь код можна знайти на моєму GitHub тут.

Завантажте набір даних за цим посиланням:

MOVIES DATASET FOR FEATURE EXTRACTION, PREDICTION (kaggle.com)

Розпочнемо з створення нотатника та простого зміни ядра на .NET Interactive. Далі завантажимо дані за допомогою старого доброго Python і pandas. Але спочатку підключимося до ядра Python.

#!connect jupyter --kernel-name pythonkernel --kernel-spec python3

Тепер завантажимо набір даних за допомогою Python та pandas (я імпортував json для майбутнього використання в наступних комірках).

Додайте нову комірку. Переконайтеся, що вибране ядро — pythonkernel (воно з’являється в нижньому правому куті комірки).
якщо ні, змініть його на python, просто клацнувши на поточне ядро та вибравши python.

import pandas as pd  
import json

Далі завантажимо дані в новій комірці:

df = pd.read_csv("movies.csv")

Тепер ви можете досліджувати дані за допомогою Python, або ж зробимо це в C#?

На поточному етапі ці дані мають багато значень null, і щоб поділитися ними без проблем, давайте перетворимо їх у чистий список словників для передачі в C#.

У новій комірці (pythonkernel):

movies_json = json.loads(df.to_json(orient='records'))

Тепер передаємо це в C#. Ви можете поділитися цією змінною, клацнувши на "Variables" і вибравши "Share". Виберіть C#, і це додасть нову комірку, яка виглядатиме ось так (c# kernel):

#!set --value @pythonkernel:movies_json --name movies_json

Чудово! Тепер переглянемо змінну. Додайте нову комірку в C# kernel:

movies_json

Ок, час скористатися повною силою C# і LINQ для дослідження даних.

Створіть наступні комірки для дослідження та очищення даних.
(C# kernel)

using System.Text.Json;  
using System;
var movies = movies_json.Deserialize<List<Dictionary<string, object>>>();
public record Movie  
{  
 public string MOVIES { get; set; }  
 public string YEAR { get; set; }  
 public List<string> GENRE { get; set; }  
 public double? RATING { get; set; }  
 public string ONE_LINE { get; set; }  
 public List<string> Directors { get; set;}  
 public List<string> Actors { get; set;}  
 public int? VOTES { get; set; }  
 public string RunTime { get; set; }  
 public string Gross { get; set; }  
}
var movie_records= (  
from m in movies  
let stars = m["STARS"].ToString()  
let pipe_split = stars.Trim().Split("|")  
let director_split = pipe_split.FirstOrDefault(p => p.Contains("Director"))  
let star_split = pipe_split.FirstOrDefault(p => p.Contains("Star"))  
let actor_part = star_split?.Split("\n").Where(x => !x.Contains("Star"))  
.Select(x => x.Trim().Trim(',')).Where(x => !string.IsNullOrEmpty(x)).Distinct().OrderBy(x => x)  
let actors = actor_part?.Any() != true ? null : actor_part.ToList()  
let director_part = director_split?.Split("\n").Where(x => !x.Contains("Director"))  
.Select(x => x.Trim().Trim(',')).Where(x => !string.IsNullOrEmpty(x)).Distinct().OrderBy(x => x)  
let directors = director_part?.Any() != true ? null : director_part.ToList()
let genre = m["GENRE"].ToString().Trim()  
let genre_split = string.IsNullOrEmpty(genre)   
? null   
: genre.Split(",").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Distinct().OrderBy(x => x)  
let genres = genre_split?.Any() != true ? null : genre_split.ToList()   
select new Movie {  
 GENRE = genres,  
 Gross = m["Gross"].ToString(),  
 MOVIES = m["MOVIES"].ToString(),  
 ONE_LINE = m["ONE-LINE"].ToString(),  
 RunTime = m["RunTime"].ToString(),  
 YEAR = m["YEAR"].ToString(),  
 RATING = double.TryParse(m["RATING"].ToString()?.Trim(),out var r) ? r : null,  
 VOTES = double.TryParse(m["VOTES"].ToString()?.Trim(),out var v) ? (int)v : null,  
 Actors = actors,  
 Directors = directors,  
}).ToList();
// заповнити порожні місця null  
(from m in movie_records  
from p in m.GetType().GetProperties()  
where p.PropertyType == typeof(string)  
let v = ((string)p.GetValue(m))?.Trim()  
let normalized_v = string.IsNullOrEmpty(v) ? null : v  
select new {m,Prop = p, Value = normalized_v}  
).ToList().ForEach(p => p.Prop.SetValue(p.m,p.Value));

У цих комірках.
я зробив:

  • завантажив дані та відобразив їх у класі Movie
  • заповнив порожні рядки значенням null, витягнув Directors і Actors із поля STARS у CSV
  • перетворив RATING і VOTES, які були розпізнані як рядки, double або int, у відповідні типи.

Тепер ми можемо повністю досліджувати movie_records, використовуючи LINQ.

Подивимося, скільки у нас тут дублікатів.

// кількість дубльованих записів  
(from m in movie_records  
group m by m.MOVIES into g  
where g.Count() > 1  
from m in g.Take(g.Count() - 1)  
select m  
).Count()

Злиємо дублікати за допомогою LINQ.

// злиття дублікатів  
movie_records =   
(from m in movie_records  
where m.MOVIES is not null  
let name = m.MOVIES  
//let director = string.Join("_",m.Directors.OrderBy(d => d).ToArray())  
group m by new {name} into g  
from m2 in g  
let groupCount = g.Count()  
let isDuplicate = groupCount > 1  
let votes = g.Max(m => m.VOTES)  
let rating = votes is null   
 ? g.Min(m => m.RATING)   
 : g.FirstOrDefault(m => m.VOTES == votes)?.RATING  
let genere = g.Where(m => m.GENRE != null).MaxBy(m => m.GENRE.Count)?.GENRE  
let actors = g.Where(m => m.Actors != null).MaxBy(m => m.Actors.Count)?.Actors  
let directors = g.Where(m => m.Directors != null).MaxBy(m => m.Directors.Count)?.Directors  
let rec = !isDuplicate ? m2   
: g.FirstOrDefault() with {  
 Actors = actors,  
 GENRE = genere,  
 Directors = directors,  
 RATING = rating,  
 VOTES = votes  
}  
where !isDuplicate || !g.Take(groupCount - 1).Any(x => ReferenceEqualityComparer.ReferenceEquals(x,m2))  
select rec  
).ToList();

І нарешті, давайте заповнимо null значення в RATING і VOTES за допомогою LINQ.

// заповнення null значень у рейтингах, обчислюючи середнє по групах  
(from m in movie_records  
where m.RATING is null   
where m.Directors != null || m.GENRE != null  
let directors = m.Directors  
let generes = m.GENRE  
let dirRatingAvg = directors is null ? null : movie_records  
.Where(x => x.RATING != null && x.Directors != null && x.Directors.Intersect(directors).Any())  
.Average(m => m.RATING)  
let genreRatingAvg = generes is null ? null : movie_records  
.Where(x => x.RATING != null && x.GENRE != null && x.GENRE.Intersect(generes).Any())  
.Average(m => m.RATING)  
let ratingAvg = dirRatingAvg ?? genreRatingAvg  
where ratingAvg != null  
select (m,ratingAvg)  
).ToList().ForEach(x => x.m.RATING = x.ratingAvg);
// заповнення null значень у голосах, обчислюючи середнє по групах  
(from m in movie_records  
where m.VOTES is null   
where m.Directors != null || m.GENRE != null  
let directors = m.Directors  
let generes = m.GENRE  
let dirVoteAvg = directors is null ? null : movie_records  
.Where(x => x.VOTES != null && x.Directors != null && x.Directors.Intersect(directors).Any())  
.Average(m => m.VOTES)  
let genreVoteAvg = generes is null ? null : movie_records  
.Where(x => x.VOTES != null && x.GENRE != null && x.GENRE.Intersect(generes).Any())  
.Average(m => m.VOTES)  
let voteAvg = (int?)(dirVoteAvg ?? genreVoteAvg)  
where voteAvg != null  
select (m,voteAvg)  
).ToList().ForEach(x => x.m.VOTES = x.voteAvg);

Поки що все добре.
нашими даними тепер можна працювати для візуалізації.

Для цього демо я використовую matplotlib та seaborn для візуалізації в Python і XPlot для візуалізації в C#.

Але спершу давайте поділимося новими очищеними movie_records з Python.

Додайте нову клітинку для Python або поділіться нею через VS Code, як обговорювалося раніше.

#!set --value @csharp:movie_records --name movie_records

Тепер імпортуємо matplotlib і seaborn і встановимо наш df на останні movie_records. (Python kernel)

import matplotlib.pyplot as plt  
import seaborn as sns
df = pd.DataFrame(movie_records)

Давайте відобразимо це, побудувавши точкову діаграму між MOVIES і RATING. (Python kernel)

plt.style.use('dark_background') # застосувати темну тему  
plt.scatter(df["MOVIES"], df["RATING"], c='red', marker='o', edgecolors='white', s=100)

Або давайте побудуємо іншу точкову діаграму між RATING і VOTES, використовуючи C#.

Спершу давайте встановимо кілька NuGet пакетів. (C# kernel)

// matplotlib добре, але давайте спробуємо C# замість цього.

це шокуюче швидко!

#r "nuget: XPlot.Plotly"  
#r "nuget: XPlot.Plotly.Interactive"

Давайте використаємо це для побудови графіка.

using XPlot.Plotly;
var chart = Chart.Plot(  
 new Scattergl() // видалити Graph.  
 {  
 x = movie_records.Select(x => x.VOTES),  
 y = movie_records.Select(x => x.RATING),  
 mode = "markers",  
 marker = new () // видалити Graph.
{  
 color = "red",  
 size = 10,  
 line = new () {color = "white",width=1}  
 }  
 }  
);  
chart.WithLayout(new()  
 {  
 title = "Scatter Plot Rating and Votes",  
 paper_bgcolor = "rgba(0, 0, 0, 0)",  
 plot_bgcolor = "rgba(0, 0, 0, 0)",  
 font = new() { color = "white" },  
 xaxis = new() { title = "Votes" },  
 yaxis = new() { title = "Rating" },  
 });  
chart

Можливо, ви помітили, що графік на C# є інтерактивним і вимагає менше часу для рендерингу (в порівнянні з Python).

Ми можемо робити ще багато чого з цим, але я не буду цього охоплювати, дивіться мій ноутбук на GitHub.

Висновок:

Використовуючи Polyglot Notebook, ви можете безперешкодно поєднувати кілька мов і використовувати мову за вашим вибором. Діліться своїми нотатками та просто працюйте з AI/Data Science на мовах, окрім Python.

Будь ласка, діліться своїми думками в коментарях.

Перекладено з: .NET + Python + JS in Jupyter? Hello Polyglot Notebooks!

Leave a Reply

Your email address will not be published. Required fields are marked *