Analyse de donnĂ©es et intelligence artificielle đ€
Bienvenue dans ce nouveau projet oĂč nous utiliserons lâIA (plus prĂ©cisĂ©ment, les grands modĂšles linguistiques comme ChatGPT, Gemini et Claude) pour nous aider Ă analyser des donnĂ©es ! Ces modĂšles sont remarquablement efficaces pour extraire, reformater, nettoyer et catĂ©goriser des donnĂ©es.
En mars 2025, jâai assistĂ© Ă une excellente session Ă NICAR prĂ©sentĂ©e par Ben Welsh et Derek Willis. Ils nous ont montrĂ© comment catĂ©goriser des demandes de remboursement Ă lâaide de ces outils fascinants.
Leur projet (en Python) a Ă©tĂ© une rĂ©vĂ©lation. Jâai donc dĂ©cidĂ© dâimplĂ©menter de nouvelles mĂ©thodes dans la bibliothĂšque Simple Data Analysis (SDA) (donnez-lui une â !) pour tirer le meilleur parti des LLM. Je vais vous montrer comment utiliser ces mĂ©thodes avec une clĂ© API Google AI Studio, mais elles fonctionnent Ă©galement avec Vertex AI et Ollama.
Ben a gentiment acceptĂ© que je reproduise son tutoriel, mais cette fois en TypeScript ! Nous utiliserons Deno et VS Code. Consultez la leçon Installation si nĂ©cessaire. Cependant, vous pouvez le faire avec lâengin dâexĂ©cution JS/TS et lâĂ©diteur de code de votre choix !
Si vous ĂȘtes bloquĂ©, il pourrait ĂȘtre utile de revoir les leçons prĂ©cĂ©dentes expliquant les bases de SDA :
Câest parti pour le code !
Configuration
Pour configurer tout ce dont nous avons besoin, utilisons setup-sda comme dans les leçons précédentes.
Créez un nouveau dossier, ouvrez-le avec VS Code et exécutez : deno -A jsr:@nshiab/setup-sda
Obtenir une clé API
Dans cette leçon, nous allons utiliser les LLM de Google. Pour interagir avec eux via notre code, nous avons besoin dâune clĂ© API. Rendez-vous sur Google AI Studio, crĂ©ez un compte si vous nâen avez pas dĂ©jĂ un, et cliquez sur Obtenir une clĂ© API
.
Cliquez ensuite sur Créer une clé API
.
Suivez les Ă©tapes suggĂ©rĂ©es. Si on vous demande dâutiliser un projet Google Cloud, sĂ©lectionnez Gemini si disponible ; sinon, crĂ©ez-en un nouveau.
Une fois que vous avez copié votre nouvelle clé API, créez un nouveau fichier .env
dans votre projet et collez-la-y. Ajoutez également le modÚle que nous utiliserons, qui est gemini-2.0-flash-lite
.
AI_KEY=your_api_key_here
AI_MODEL=gemini-2.0-flash-lite
.env
est un fichier que nous utilisons habituellement pour les variables dâenvironnement, qui sont souvent des identifiants qui ne doivent pas ĂȘtre partagĂ©s. Lorsque vous configurez tout avec setup-sda
, ce fichier est automatiquement ajouté à .gitignore
, il ne sera donc pas commité par Git et ne sera pas poussé sur GitHub.
Maintenant, il nous suffit de charger ces variables en mémoire.
Installez la bibliothĂšque @std/dotenv
de lâĂ©quipe Deno en exĂ©cutant deno add jsr:@std/dotenv
.
Ensuite, ajoutez import "@std/dotenv/load";
en haut de votre fichier sda/main.ts
. Maintenant, chaque fois que vous exécuterez ce code, Deno chargera automatiquement les variables de votre fichier .env
!
Et nous sommes prĂȘts Ă dĂ©coller ! đ
Ă propos de cette APIâŠ
Avant dâexĂ©cuter du code, il y a quelque chose que je veux vous montrer.
Dans Google AI Studio, aprÚs avoir cliqué sur Obtenir une clé API
, vous avez accĂšs Ă votre Facturation de votre plan API
. Sélectionnez le modÚle que nous allons utiliser : Gemini 2.0 Flash Lite
.
LĂ , vous verrez que vous avez un compte gratuit qui vous permet de faire 30 requĂȘtes par minute avec ce modĂšle (cela a peut-ĂȘtre changĂ© depuis que jâai Ă©crit ceci).
Et vous remarquerez Ă©galement que⊠les donnĂ©es que vous allez envoyer seront utilisĂ©es par Google ! Alors, soyez trĂšs prudent : nâenvoyez pas de donnĂ©es sensibles via cette API !
Comme vous le savez peut-ĂȘtre dĂ©jĂ , rien nâest vraiment gratuit dans la vie⊠đ„Č
Si vous avez un compte pro ou entreprise, ils dĂ©clarent quâils nâutiliseront pas vos donnĂ©es pour amĂ©liorer leurs produits. Mais si vous avez besoin dâune confidentialitĂ© totale, je vous montrerai Ă la fin comment utiliser Ollama pour exĂ©cuter des modĂšles localement sur votre machine.
Les données
Lâobjectif du projet est de catĂ©goriser des demandes de remboursement. Nous allons utiliser les donnĂ©es CSV du tutoriel de Ben. Elles sont hĂ©bergĂ©es sur GitHub et contiennent 250 lignes.
Ben a manuellement catégorisé la colonne payee
. Nous demanderons Ă notre modĂšle de faire le mĂȘme exercice, puis nous comparerons les rĂ©sultats humains et ceux de lâIA.
Pour récupérer les données, nous créons une nouvelle table data
et mettons en cache les résultats de loadData
, qui récupÚre les données de GitHub. Comme les données ne changeront pas, la mise en cache est logique. Elles seront stockées localement dans le dossier .sda-cache
. Lorsque vous configurez tout avec setup-sda
, ce dossier caché est ajouté à votre .gitignore
.
Nous affichons également la table dans le terminal.
Pour exécuter ce code et le surveiller, exécutez deno task sda
dans votre terminal. à chaque fois que nous allons modifier le code et le sauvegarder, il sera automatiquement exécuté. Pratique!
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.logTable();
await sdb.done();
Si vous voulez supprimer le cache, vous pouvez supprimer manuellement .sda-cache
ou exécuter deno task clean
dans votre terminal.
Envoi de données au modÚle
Pour envoyer des données et des instructions au modÚle, nous devons utiliser la méthode aiRowByRow
sur notre table. Si vous ĂȘtes curieux, vous trouverez la documentation ici.
Son utilisation est assez simple :
- Tout dâabord, passez la colonne contenant les donnĂ©es que vous souhaitez envoyer Ă lâIA (ici, câest
payee
). - Ensuite, indiquez le nom de la nouvelle colonne qui sera créée pour stocker les rĂ©sultats de lâIA (ici, nous la nommons
categoryAI
). - Et enfin, tapez votre prompt. Ici, nous demandons au LLM de catégoriser le
payee
.
Le quatriĂšme argument est facultatif. Câest un objet avec quelques options :
rateLimitPerMinute
garantira que nous respectons notre quota dâAPI. Si nous envoyons des requĂȘtes trop rapidement, la mĂ©thode attendra la durĂ©e nĂ©cessaire pour Ă©viter que les requĂȘtes ne soient rejetĂ©es.verbose
affichera des informations supplémentaires dans le terminal pendant le traitement des données.
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.logTable();
await sdb.done();
Si vous exécutez ce code, vous verrez votre prompt ainsi que des instructions supplémentaires ajoutées par la méthode.
Lâoption verbose
ajoute des informations supplĂ©mentaires utiles. Les tokens reprĂ©sentent la quantitĂ© dâinformations envoyĂ©es et reçues par lâIA. Sur cette base, la mĂ©thode vous donne une estimation du coĂ»t de la requĂȘte si vous avez un compte avec paiement Ă lâutilisation.
Actuellement, les donnĂ©es sont classifiĂ©es par le modĂšle dâIA, mais⊠câest un peu lent.
Parce que nous avons 250 lignes de donnĂ©es, et que nous ne pouvons envoyer que 30 requĂȘtes par minute, classer lâensemble des donnĂ©es prendra⊠plus de 8 minutes !
Nous pouvons sûrement accélérer cela !
Accélérer le processus
Lots
Au lieu dâenvoyer une ligne Ă la fois, nous pourrions en envoyer un lot !
Dans le code ci-dessous, nous utilisons lâoption batchSize
pour envoyer 10 lignes Ă la fois. Maintenant, le traitement de lâensemble des donnĂ©es prend moins dâune minute !
Câest gĂ©nial, mais nous pouvons pousser cela un peu plus loinâŠ
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
batchSize: 10,
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.logTable();
await sdb.done();
Concurrence
Lâune des nombreuses fonctionnalitĂ©s formidables de TypeScript et JavaScript est la concurrence. Au lieu de faire une seule requĂȘte Ă lâAPI Ă la fois, vous en envoyez plusieurs simultanĂ©ment et attendez ensuite les rĂ©sultats en parallĂšle.
Tirer parti de cette fonctionnalitĂ© du langage peut accĂ©lĂ©rer lâexĂ©cution de notre code !
Puisque nous avons 250 lignes de donnĂ©es, nous pourrions envoyer des lots de 17 lignes avec 15 requĂȘtes concurrentes pour traiter toutes nos donnĂ©es en moins de⊠2 secondes !
Ces 15 requĂȘtes nous maintiennent en dessous de la limite de quota de 30, ce qui est important.
Un conseil : pour exĂ©cuter ce code, assurez-vous de nâavoir pas envoyĂ© de requĂȘtes depuis au moins une minute ; sinon, vous pourriez atteindre la limite de quota du plan gratuit.
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB({ logDuration: true });
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
batchSize: 17,
concurrent: 15,
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.logTable();
await sdb.done();
Mise en cache
Il y a une derniĂšre astuce que nous pouvons utiliser pour rendre notre code plus efficace : le mise en cache.
Nous pourrions stocker les rĂ©sultats envoyĂ©s par lâIA dans des fichiers localement. Si nous rĂ©exĂ©cutons notre code et que le prompt/les donnĂ©es nâont pas changĂ©, nous pourrions utiliser ce que nous avons stockĂ© sur notre machine au lieu dâattendre Ă nouveau le modĂšle dâIA.
Câest exactement ce que lâoption cache
vous permet de faire.
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB({ logDuration: true });
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
batchSize: 17,
concurrent: 15,
cache: true,
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.logTable();
await sdb.done();
Si vous exécutez ce code, vous remarquerez un nouveau dossier caché .journalism-cache
. Les fichiers quâil contient sont des fichiers JSON stockant les rĂ©ponses de lâIA. Il fonctionne comme le dossier .sda-cache
précédent, mais il est créé par la fonction askAI
de ma bibliothĂšque journalism
, que la méthode aiRowByRow
utilise pour interagir avec le modĂšle dâIA.
Lors de la premiĂšre exĂ©cution, les requĂȘtes sont faites et le script prend autant de temps dâexĂ©cution quâauparavant.
Mais si vous réexécutez ce script (CTRL
+ S
dans main.ts
devrait faire lâaffaire), il est maintenant beaucoup plus rapide ! Il ne prend que quelques millisecondes ! Au lieu de faire les requĂȘtes, le code rĂ©cupĂšre les donnĂ©es stockĂ©es sur votre machine.
Bien sĂ»r, si vous modifiez le prompt ou les donnĂ©es envoyĂ©es au modĂšle, la mĂ©thode saura quâelle doit faire de nouvelles requĂȘtes et stocker de nouveaux rĂ©sultats. Mais si vous ne touchez pas aux arguments aiRowByRow
modifiant les requĂȘtes, vous pouvez travailler en toute sĂ©curitĂ© sur dâautres choses sans Ă©puiser vos quotas dâAPI et Ă pleine vitesse !
Si vous voulez supprimer le cache, vous pouvez supprimer manuellement .journalism-cache
ou exécuter deno task clean
dans votre terminal.
Tester les résultats
Tests
Bien quâils sâamĂ©liorent Ă un rythme incroyable, les LLM ne comprennent pas toujours correctement vos instructions. Et lorsque vous faites des dizaines, des centaines ou des milliers de requĂȘtes, il y a toujours une chance quâils renvoient quelque chose dâinattendu.
Câest pourquoi les tests sont importants.
Dans le code ci-dessous, nous utilisons un test
(une simple fonction) pour vérifier si la catégorie renvoyée pour chaque entrée correspond aux catégories attendues.
Sâil y a une catĂ©gorie inattendue, nous lançons une erreur, et avec lâoption retry
, nous spĂ©cifions que nous voulons rĂ©essayer cinq fois avant dâabandonner et de tout arrĂȘter.
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB({ logDuration: true });
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
batchSize: 17,
concurrent: 15,
cache: true,
test: (dataPoint) => {
if (typeof dataPoint !== "string") {
throw new Error("Not a string");
}
if (!["Restaurant", "Bar", "Hotel", "Other"].includes(dataPoint)) {
throw new Error("Unexpected category");
}
},
retry: 5,
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.logTable();
await sdb.done();
De mon cĂŽtĂ©, aucune erreur nâa Ă©tĂ© gĂ©nĂ©rĂ©e. Ăa a lâair bon !
Précision
Puisque nous avons testé les résultats, nous savons que nous avons les bonnes catégories, mais ont-elles été correctement attribuées ?
Il est temps de vérifier la précision du modÚle !
Puisque Ben a déjà classé les entreprises dans la colonne category
et que nous avons mis les rĂ©sultats de lâIA dans la colonne categoryAI
, utilisons quelques méthodes de SDA pour ce faire !
PremiĂšrement, crĂ©ons une nouvelle colonne pour vĂ©rifier si lâIA a retournĂ© la bonne rĂ©ponse, en supposant que Ben avait raison.
En utilisant cette colonne, nous pouvons ensuite enregistrer toutes les occurrences de rĂ©ponses diffĂ©rentes entre Ben et lâIA.
Nous pouvons Ă©galement calculer la proportion de rĂ©ponses correctes de lâIA.
import "@std/dotenv/load";
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB({ logDuration: true });
const data = sdb.newTable("data");
await data.cache(async () => {
await data.loadData(
"https://raw.githubusercontent.com/palewire/first-llm-classifier/refs/heads/main/_notebooks/sample.csv",
);
});
await data.aiRowByRow(
"payee",
"categoryAI",
`Categorize the payee into one of the following categories: Restaurant, Bar, Hotel or Other. `,
{
batchSize: 17,
concurrent: 15,
cache: true,
test: (dataPoint) => {
if (typeof dataPoint !== "string") {
throw new Error("Not a string");
}
if (!["Restaurant", "Bar", "Hotel", "Other"].includes(dataPoint)) {
throw new Error("Unexpected category");
}
},
retry: 5,
rateLimitPerMinute: 30,
verbose: true,
},
);
await data.addColumn("correctCategory", "boolean", `category === categoryAI`);
await data.logTable({
conditions: `correctCategory === false`,
nbRowsToLog: "all",
});
await data.summarize({
categories: "correctCategory",
});
await data.proportionsVertical("count", "perc");
await data.logTable();
await sdb.done();
Et⊠voici le résultat !
Si nous vĂ©rifions les lignes avec des rĂ©ponses diffĂ©rentes entre Ben et lâIA, nous pouvons remarquer que⊠Ben a fait quelques erreurs !
Par exemple, El POLLO INKA et ELLA DINNING ROOM devraient ĂȘtre des restaurants. Et FAIRMONT BATTERY WHARF devrait ĂȘtre un hĂŽtel. (Si vous ne voyez pas ces erreurs, câest peut-ĂȘtre parce que Ben les a corrigĂ©es !)
Pour le reste, comme nous nâavons pas les adresses, nous accorderons Ă Ben le bĂ©nĂ©fice du doute.
Cela signifie que le modÚle a atteint un taux de précision supérieur à 94 % ! Pas mal compte tenu de la simplicité de nos instructions et de la rapidité de traitement.
Selon ce sur quoi vous travaillez, peut-ĂȘtre que ce niveau de prĂ©cision est suffisant si vous le divulguez dans votre publication. Une chose est certaine : le faire Ă la main aurait Ă©tĂ© beaucoup plus lent (ou tout simplement impossible sâil y avait des millions de lignes), et vous auriez Ă©galement fait des erreurs !
Si vous nâĂȘtes pas satisfait de la prĂ©cision, vous pouvez affiner votre prompt, donner des exemples dans vos instructions, prĂ©traiter les donnĂ©es ou simplement passer Ă un autre modĂšle. Mais au moins, en vĂ©rifiant la prĂ©cision, vous ne ferez pas de changements Ă lâaveugle ! Vous saurez quand les choses sâamĂ©liorent et quand elles sâaggravent.
Conclusion
Ici, nous avons classifié des données avec un modÚle Gemini. Mais vous pouvez faire bien plus que cela, comme nettoyer, formater et extraire des données avec des LLM !
Et en utilisant la méthode aiRowByRow
de SDA, vous pouvez le faire facilement, en quelques lignes de code, à grande échelle !
Si vous avez des donnĂ©es sensibles que vous ne voulez pas envoyer Ă Google ou Ă un autre fournisseur dâIA, vous pouvez Ă©galement utiliser Ollama. Il suffit de le dĂ©marrer et de passer les variables suivantes dans votre fichier .env
. SDA basculera automatiquement vers lâutilisation du modĂšle local installĂ© sur votre machine pour traiter les donnĂ©es.
OLLAMA=true
AI_MODEL=gemma3:4b
Parfois, les modÚles open-source légers ont plus de difficulté à renvoyer directement des listes (ce qui est la réponse attendue pour aiRowByRow
). Si câest le cas, jâai implĂ©mentĂ© lâoption clean
qui vous permet de modifier directement la réponse du LLM, par exemple en la restructurant en une liste.
Bien quâil sâagisse dâune technologie exceptionnelle, nâoubliez pas que les rĂ©sultats ne sont pas garantis avec les LLM. Et, dans mes projets, plus les lots sont grands, plus les rĂ©sultats ont tendance Ă ĂȘtre mauvaisâŠ
Mais malgré tout, ils peuvent considérablement accélérer vos recherches et vos analyses. Ils sont incroyablement rapides et faciles à utiliser.
Alors, si vous les essayez dans lâun de vos projets, faites-le moi savoir !
Et merci encore à Ben Welsh et Derek Willis qui ont créé la version initiale de ce tutoriel.