Skip to content

Rust implementation - #248

Draft
ThibaudDauce wants to merge 14 commits into
mainfrom
rust_implementation
Draft

Rust implementation#248
ThibaudDauce wants to merge 14 commits into
mainfrom
rust_implementation

Conversation

@ThibaudDauce

@ThibaudDauce ThibaudDauce commented Apr 9, 2026

Copy link
Copy Markdown
Contributor
Fichier Lignes Python Rust avant Rust après Speedup
MN_07 4.3M 27.4s 1.9s 1.4s 19.9x
ValeursFoncieres-2024 3.5M 33.9s 8.2s 6.8s 5.0x
ValeursFoncieres-2025 1.4M 11.5s 3.3s 2.7s 4.2x
irve 203k 5.6s 0.8s 0.6s 8.7x
joconde 993k 37.4s 10.9s 9.2s 4.1x

Avec num_rows=-1 Python est encore plus lent qu'avant (il analyse tout au lieu d'échantillonner). Les speedups Rust augmentent — entre 4x et 20x.

Les FAIL sur les gros fichiers viennent tous de bugs/limitations du chunking Python, pas de différences d'implémentation métier. Voici la liste exhaustive :

1. total_lines tronqué

Python arrête de lire le fichier dès que tous les formats sont éliminés (early stop dans le chunking). total_lines ne compte que les lignes lues, pas le total réel. Rust lit tout → total correct.

  • MN_07 : Python 3 951 382, Rust 4 341 382
  • joconde : Python 910 000, Rust 992 809

2. categorical divergent

Python calcule les catégoriques sur les value_counts accumulés des chunks lus. Comme l'early stop tronque la lecture, les value_counts sont incomplets → moins de valeurs uniques → plus de colonnes marquées catégoriques (ou l'inverse). Rust a les vrais value_counts sur tout le fichier.

  • irve : Python 19 catégoriques, Rust 42
  • joconde : Python 5, Rust 51
  • ValeursFoncieres : Python 11, Rust 41-42

3. Commune et Code postal non détectés par Python

Sur ValeursFoncieres, Python ne détecte pas commune ni code_postal car le chunking avec moyennes pondérées dilue les scores. Les chunks avec beaucoup de valeurs vides font baisser la moyenne en dessous du seuil. Rust calcule le score exact sur toutes les données → détection correcte.

4. Scores légèrement différents

Sur joconde, Ecole_pays a un score de 1.20 en Python vs 1.04 en Rust, et Ville 1.50 vs 1.47. La moyenne pondérée par chunks donne des résultats différents du calcul exact. Rust est plus précis.

5. Surface Carrez du 5eme lot score différent

Python donne 0.25-0.30, Rust donne 1.0. Cette colonne est quasi-vide (quelques valeurs sur 3.5M lignes). Le chunking Python calcule un score partiel, Rust voit que les rares valeurs non-vides matchent toutes → score 1.0.


En résumé : zéro différence métier dans l'implémentation des formats. Toutes les différences viennent du fait que Python échantillonne/chunke et Rust analyse tout. Sur les petits fichiers (< 10 000 lignes, pas de chunking), Python et Rust donnent des résultats identiques (14/14 PASS).

@Pierlou

Pierlou commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Ah oui on est sur autre chose là

@ThibaudDauce

Copy link
Copy Markdown
Contributor Author

Ah oui on est sur autre chose là

Je fais juste mumuse, ça me permet de mieux comprendre csv-detective, je suis assez déçu des perfs pour le moment, pas sûr que ça vaille la complexité (même si on gagne en fiabilité car on analyse tout le fichier au lieu de faire du sampling…)

@Pierlou

Pierlou commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Comment ça du sampling ? En python on analyse tout (en chunks, mais tout) (à condition de mettre nrows=-1 ce qui est le cas dans hydra)

@ThibaudDauce

Copy link
Copy Markdown
Contributor Author

Comment ça du sampling ? En python on analyse tout (en chunks, mais tout) (à condition de mettre nrows=-1 ce qui est le cas dans hydra)

J'utilisais pas nrows, si je désactive ça, c'est un peu mieux. Mais j'ai toujours des différences de résultats, d'après Claude :

Parce que Python fait quand même du chunking sur les gros fichiers. num_rows=-1 désactive l'échantillon initial de 500 lignes, mais le chunking reste actif pour les fichiers > 10 000 lignes (CHUNK_SIZE).

Le flow avec num_rows=-1 sur un gros fichier :

  1. Lire les 10 000 premières lignes → premier score
  2. Relire le fichier en chunks de 10 000, batches de 10 → moyennes pondérées
  3. Early stop si tous les formats éliminés → total_lines tronqué
  4. Categorical calculé sur les value_counts accumulés des chunks lus → incomplet si early stop

C'est le même problème qu'avant — num_rows=-1 ne change rien au chunking. Le chunking est toujours là et l'early stop tronque toujours total_lines.

Pour avoir des résultats Python vraiment comparables, il faudrait que Python lise tout le fichier sans early stop. Mais il n'y a pas d'option pour ça dans csv-detective Python.

@Pierlou

Pierlou commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Effectivement l'early stop rend erronnés total_lines et categorical, je vais voir pour améliorer ça, mais ça ne change pas la fiabilité de l'analyse des colonnes

@ThibaudDauce

Copy link
Copy Markdown
Contributor Author

Effectivement l'early stop rend erronnés total_lines et categorical, je vais voir pour améliorer ça, mais ça ne change pas la fiabilité de l'analyse des colonnes

J'ai l'impression que j'ai pas les mêmes résultat pour les chunks, non ? On ne fait pas des déductions sur le premier chunk qui font qu'on regarde pas les autres ou différemment ? Peut-être que ma compréhension n'est pas bonne…

@Pierlou

Pierlou commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Effectivement dans l'analyse par chunks on est un peu plus stricts que pour l'analyse en une fois : après la première analyse pour initialiser return_table, on drop pour chaque colonne tous les formats qui ont donné un score de 0, ce qui revient à "si aucune valeur des 10k premières lignes n'est valide pour un format, alors on ne reteste pas les lignes suivantes pour ce format". On se prive donc de cas comme une colonne de 100k valeurs dont toutes les valeurs valides pour un format seraient à partir de la ligne 10001 (et il y a assez de valeurs valides pour atteindre la proportion minimale), ce qui me semble proportionné, mais n'est pas parfait.
Il y a aussi le fait qu'on passe le score d'un format ou une colonne d'un chunk à 0 si le score est inférieur à la proportion minimale attendue. Globalement csv-detective pour les gros fichiers fait l'hypothèse d'une répartition relativement uniforme des valeurs (in)valides pour gagner du temps. Si on veut être parfaitement rigoureux à ce niveau, il faut stocker les scores réels (sans écrêtement) et passer toutes les colonnes au crible de tous les formats dont la proportion minimale est < 1 (sauf si on a parcouru "assez" du fichier pour savoir qu'on sera en dessous quoiqu'il en fait ce n'est pas possible de le savoir vu qu'on parse le fichier sans savoir quand il s'arrête, donc il faut tout parser jusqu'au bout)

@ThibaudDauce

Copy link
Copy Markdown
Contributor Author

Effectivement dans l'analyse par chunks on est un peu plus stricts que pour l'analyse en une fois : après la première analyse pour initialiser return_table, on drop pour chaque colonne tous les formats qui ont donné un score de 0, ce qui revient à "si aucune valeur des 10k premières lignes n'est valide pour un format, alors on ne reteste pas les lignes suivantes pour ce format". On se prive donc de cas comme une colonne de 100k valeurs dont toutes les valeurs valides pour un format seraient à partir de la ligne 10001 (et il y a assez de valeurs valides pour atteindre la proportion minimale), ce qui me semble proportionné, mais n'est pas parfait. Il y a aussi le fait qu'on passe le score d'un format ou une colonne d'un chunk à 0 si le score est inférieur à la proportion minimale attendue. Globalement csv-detective pour les gros fichiers fait l'hypothèse d'une répartition relativement uniforme des valeurs (in)valides pour gagner du temps. Si on veut être parfaitement rigoureux à ce niveau, il faut stocker les scores réels (sans écrêtement) et passer toutes les colonnes au crible de tous les formats dont la proportion minimale est < 1 (sauf si on a parcouru "assez" du fichier pour savoir qu'on sera en dessous quoiqu'il en fait ce n'est pas possible de le savoir vu qu'on parse le fichier sans savoir quand il s'arrête, donc il faut tout parser jusqu'au bout)

Une autre différence qui peut t'intéresser :

Ligne 253 : len(values) <= MAX_NUMBER_CATEGORICAL_VALUES — Python en mode chunk ne teste que ≤ 25 uniques, pas le seuil de 5%. Le OR ≤ 5% n'est pas implémenté dans le path chunk.

C'est un autre bug Python. En mode non-chunk (petits fichiers), detect_categorical_variable utilise les deux critères. En mode chunk, seul le critère ≤ 25 est utilisé.

nom_amenageur avec 3729 uniques ne passe pas le seuil de 25, mais passe le 5%. Python en mode chunk ne regarde que le 25 → pas catégorique. Rust utilise les deux critères → catégorique.

C'est pour ça qu'on a 42 vs 19 : les 23 colonnes supplémentaires de Rust ont entre 99 et 9279 uniques (toutes < 5% de 202k), mais > 25.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants