Bravo ! Vous êtes "Lexpage Compliant" !    —  GDI

Discussions

Python : apprentissage, ide, etc

yaug 1338 Spammeur
Reprise automatique du message précédent.
Pour la génération de pdf, je suis tombé sur ce lien où jinja est utilisé justement.
La solution proposé me plait pas mal.
Guybrush 7783 Bob
Je ne connaissais pas Weasyprint. Mais ça a l'air d'être la même philosophie que ce que je propose ci-dessus : tu génères tes fichiers "sources" via Jinja2, et puis tu utilises un outil pour convertir le résultat en PDF (Latex pour les .tex, weasyprint pour les .html). Je pense que c'est en effet le plus simple (et Jinja2 est vraiment très simple et sympa à utiliser, une fois que tu y as gouté, tu as envie de l'utiliser un peu partout :-D).
yaug 1338 Spammeur
Yeap jinja a l'air sympa.
Je garde cela sous le coude.

Pour le moment j'essaye de piger pandas car je vais m'en manger un max vu que mon taff se base sur des fichiers excel à la pelle.
Guybrush 7783 Bob
Pandas est à la fois simple et complexe. C'est simple de créer/interroger un DataFrame, mais c'est complexe à utiliser "proprement" de sorte à avoir un code simple à lire, relire et modifier. Surtout si tu dois cumuler les transformations, et que tu dois produire des graphiques en sortie.

Une astuce sympa en terme d'écriture, et qui permet de garder un code relativement lisible est d'éviter la multiplication des variables/df autant que possible, par exemple en chainant les opérations.

Les méthodes pratiques pour ça, c'est .assign qui permet de créer/écraser des colonnes sur base d'autres colonnes à la volée, ou encore .pipe qui permet d'appliquer un traitement à la volée sur un dataframe. Les deux prennent un callable en paramètre :

df.assign(new_column=lambda df: df['old_column'] * 2) par exemple, ou encore df.pipe(lambda df: 2 * df (le premier crée une nouvelle colonne qui est le double d'une autre, le second crée un df où tout est doublé).

Une astuce sympa aussi est que l'indexation (via [....]) accepte aussi un callable qui, une fois appelé, doit retourner une série de booléens. Les "True" servent à indiquer quels indexes doivent être conservés. On peut donc assez facilement sélectionner des rows de façon avancée : df[lambda df: df['col'] >= 0]. Si on veut plus de lisibilité, il y a aussi .query qui permet d'écrire la requête sous forme de texte, par exemple df.query('col >= 0'). Dépendant de l'usage, l'un est plus approprié/lisible que l'autre.


Quand tu as ça, tu peux facilement cumuler des choses :
new_df = (
old_df
[lambda df: df['column'] >= 0]
.assign(new_column=....)
.pipe(lambda df:
df.merge(other_df, .....)
)
.assign(somme=lambda df: df.sum(axis=1))
....
)
Ainsi, toutes tes transformations/manipulations sont faites "d'une seule traite", et tu stockes juste le résultat fini dans une variable (et les opérations intermédiaires disparaissent de la mémoire et ne pourrissent pas le namespace). Avec cette approche (et l'indentation/retour à la ligne), tu peux même facilement intégrer des commentaires pour expliquer chaque "étape" intermédiaire :
new_df = (
old_df
# Keep positive values
[lambda df: df['column'] >= 0]
# Create a new column that is the result of...
.assign(new_column=....)
# Merge dataframe with ...
.pipe(lambda df:
(2 * df).merge(other_df, .....)
)
# Sum everything line by line
.assign(sum=lambda df: df.sum(axis=1))
....
)
Les commentaires peuvent paraître redondants avec le code dans cet exemple, mais la multiplication des fonctions lambda rend parfois la lecture du code difficile, alors un commentaire n'est jamais de trop dans ces cas-là ;-)
yaug 1338 Spammeur
Pour en ce moment même étudier et comprendre un process de calcul financier et ne me basant juste sur le code produit (sans commentaires), oui c'est à la fois simple et compliqué pandas :D
Surtout quand tu n'as pas l'habitude des notations pythons du genre : df.assign(new_column=lambda df: df['old_column'] * 2)

ça a l'air d'être un outil puissant et les quelques blogs que je trouve là dessus me font penser qu'une fois passé le double gap de l'apprentissage de python et de pandas, je devrais pouvoir faire quelques trucs sympa.
Mais pour le moment je suis en cours de descente dans ces 2 gaps :d
Guybrush 7783 Bob
yaugSurtout quand tu n'as pas l'habitude des notations pythons du genre : df.assign(new_column=lambda df: df['old_column'] * 2)
Bon, ça ne va pas fondamentalement t'aider, mais ça donne des pointeurs :-)

df -> la variable (un dataframe, par convention "df")
.assign -> la méthode pour "assigner des nouvelles colonnes"
(new_column=) -> Python supporte le passage de paramètres par position ou par nom, et dans les deux cas, on peut récupérer tout ça sous forme de liste ou de mapping param->value, donc ici, "new_column" est récupéré comme clé du mapping et la lambda derrière est la valeur, ce qui permet à pandas de comprendre qu'il doit créer une nouvelle colonne du nom "new_column" dont la valeur est défini par la lambda (une fonction anonyme).
lambda .... -> fonction anonyme, dans le cas présent, la fonction reçoit un paramètre (le dataframe sur lequel on appelle .assign) et retourne une série de données (les valeurs de la colonne qu'on souhaite créer).
df['old_column'] -> le ['old_column'] est un "get index" en Python (en gros, ce que tu passes entre "[]" est donné en paramètre à une fonction définie sur le dataframe. Dans le cas présent, c'est une chaîne de caractères et pandas comprend que ça veut dire "j'veux toute la série de donnée dans la colonne 'old_column').
yaug 1338 Spammeur
Nouvelle question.
Pour ce qui est d'Elasticsearch le client semble pas mal, donc je ne vais pas avoir trop de problèmes.

Je vais sans doute avoir aussi besoin d'utiliser une base MySQL, et du coup, je cherche un ORM valable. J'ai vu passer peewee notamment, mais avez vous des recommandations autres ?
Guybrush 7783 Bob
Le plus complet/complexe est sans doute celui d'SQLAlchemy. Mais il est complexe (la lib est complexe, mais c'est la plus complète qu'on puisse trouver, quelque soit le langage).

Personnellement, j'ai pas mal aimé l'ORM de Django, mais je ne pense pas qu'il puisse être utilisé "facilement" en dehors du cadre de Django. A part ça, j'aime pas les ORM en général, mais je me suis beaucoup intéressé à Peewee pendant tout un moment parce que c'est plutôt élégant, bien pensé, et que le gars est sympa :-D

Je me souviens qu'y avait un truc qui m'avait embêté c'était le fait de devoir passer la "database" lors de la création des modèles (donc en gros, tu devais avoir une ref vers la db en "static" dans le code). Je ne sais pas si ça a changé (le projet a évolué depuis que j'ai vu ça), mais je sais que ça peut se contourner (je n'ai jamais essayé, mais en gros, c'est une sorte d'injection de dépendance à faire, donc il doit y avoir moyen de s'en sortir très élégamment à coup de décorateurs ou autre).

TL;DR: Oui, je te conseille Peewee si tu ne peux pas te passer d'un ORM.

Dans les alternatives sympa (mais à nouveau, j'ai *pas* testé !!), y a PoneyORM qui utilise la syntaxe des générateurs en Python pour faire les requêtes. Ca avait l'air assez sympa mais ce qui m'avait un peu "refroidi", c'est le fait que la lib fait une introspection du code à la volée pour justement convertir ces expressions, ce qui donne un peu l'impression d'un "bricolage".
yaug 1338 Spammeur
Pour peewee de ce que j'ai vu ouaip il faut toujours injecter la db dans ton model. Je n'aime pas trop non plus mais le reste a l'air sympa tout de même et je vais sans doute m'orienter vers ça.
Guybrush 7783 Bob
Si mes souvenirs sont bons, il est possible de déclarer un proxy comme database afin de ne pas avoir à établir la connexion lors de la "création" des modèles (on se comprend), ce qui permet de changer la valeur de cette dernière au runtime.

La façon la plus simple de gérer la database est de simplement la déclarer dans un fichier (genre "conf.py"), et ensuite d'importer ce module dans chaque fichier où tu déclares un modèle. Dans ton "module principal" (où tu orchestres tout), tu charges conf et tu remplaces à la volée la db à utiliser (via le mécanisme de proxy ou autre) avant d'établir la connexion.

C'est juste "moche" qu'on ne puisse pas faire une vraie injection de dépendances dans Peewee (bon, on peut s'en sortir tout de même en utilisant des meta-classes ou des décorateurs, mais on a toujours besoin d'importer quelque chose systématiquement pour chaque modèle de données "juste pour ça").
yaug 1338 Spammeur
Bon, je suis confronté à une erreur qui semble commune mais que je n'arrive tout de même pas à gérer.
J'ai un fichier excel que je suis en train d'importer en base.
J'ai une des colonnes qui s'appelle "20_Contract size for derivatives"

Au niveau des valeurs, cela va souvent être vide, et parfois j'aurais un chiffre : 1, 100000, 34561.8

Je lis mon fichier avec pandas et je boucle les lignes de mon dataframe pour utiliser les données.
Je pensais pouvoir faire un row['20_Contract size for derivatives'] mais il me sort toujours une erreur :
KeyError: '20_Contract size for derivatives'
Alors même que j'ai bien une entrée à ce nom si je fais un print(row) :
20_Contract size for derivatives NaN

Si je comprend bien ma problématique, c'est qu'il considère que je ne peux pas accéder à ma colonne si j'ai une valeur nulle.
Du coup je suis passé par row.get('20_Contract size for derivatives')
Cela me renvoie un None, parfait.

Sauf qu'en fait non.
Si dans mon tuple j'ai cette valeur :
20_Contract size for derivatives                                                            1
et que je cherche à afficher le contenu de la colonne, j'ai quand même un "None" et l'accès en mode tableau me renvoie tout de même une KeyError

Une idée ?
Cela doit être évident mais ça fait une heure que je patine juste sur cette colonne, du coup j'ai du mal comprendre un truc je pense.
Merci :)


Ce message a été modifié 1 fois. Dernière modification : 12 septembre 2018 à 16:43 par yaug.

Répondre

Vous devez être inscrit et identifié.