Identifier les résultats de calculs

pandas ?

a marqué ce sujet comme résolu.

Bonjour,

Je souhaite prévoir la production laitière du troupeau de mon oncle et je dispose pour cela de la table suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE IF NOT EXISTS CrudeData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    date DATE NOT NULL,
    prod REAL NOT NULL, -- L
    cons REAL NOT NULL, -- kg
    lact INTEGER NOT NULL,
    day INTEGER NOT NULL,
    UNIQUE (cow, date)
);

Une lactation est un cycle de production (du vêlage au tarissement) et le champ day contient le numéro du jour dans la lactation correspondante.

Il me faut alors effectuer (avec Python) certains calculs sur ces données : moyenne mobile, différenciation

Comme j'ai besoin des résultats de ces calculs pour 1. les réutiliser dans d'autres calculs (différencier les données après passage d'une moyenne mobile par exemple) 2. générer des vues (ici, des PDF) 3. éviter de refaire les calculs plus tard (pas primordial, les calculs n'étant pas lourds), je stocke ça en base :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
CREATE TABLE IF NOT EXISTS SmoothedData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    source INTEGER NOT NULL, -- The corresponding line in the CrudeData table
    prod REAL NOT NULL,
    step INTEGER,
    FOREIGN KEY(source) REFERENCES CrudeData(id),
    UNIQUE (source, step)
);

CREATE TABLE IF NOT EXISTS DifferencedData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    source INTEGER NOT NULL, -- The corresponding line in the CrudeData table
    prod REAL NOT NULL,
    degree INTEGER, -- How many times data have been differenced
    FOREIGN KEY(source) REFERENCES CrudeData(id),
    UNIQUE (source, degree)
);

Seulement, il y a maintenant un problème. Je voudrais stocker la fonction d'autocorrélation (ACF), qui consiste, pour un ensemble de valeurs, en un tableau de flottants, de taille variant avec celle des données d'entrée. Du coup, j'ai fait cela :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE IF NOT EXISTS Tables(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR NOT NULL,
    UNIQUE (name)
);

CREATE TABLE IF NOT EXISTS ACF(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    source INTEGER NOT NULL,
    cow INTEGER NOT NULL,
    acf BLOB,
    FOREIGN KEY(source) REFERENCES Tables(id),
    UNIQUE (cow, source)
);

INSERT OR IGNORE INTO Tables (name) VALUES ('CrudeData');
INSERT OR IGNORE INTO Tables (name) VALUES ('SmoothedData');
INSERT OR IGNORE INTO Tables (name) VALUES ('DifferencedData');
INSERT OR IGNORE INTO Tables (name) VALUES ('ACF');

En effet, je me suis dit que je ne souhaiterais calculer l'ACF que pour un certain type de données (brutes, lissées, différenciées…) et pour des données d'entrée ne provenant que d'une seule table. C'était sans compter les paramètres du genre step et degree (en effet, une table contient plus types de données : par exemple, différenciées une fois, deux fois…).

Du coup, comment puis-je m'organiser pour pouvoir identifier facilement, par exemple, l'ACF des productions différenciées une fois, deux fois, lissées deux fois, trois fois… ? Sachant que je veux pouvoir ajouter d'autres opérations en plus du lissage et de la différenciation.

J'utilise actuellement une base SQL parce que les requêtes SQL me permettent de sélectionner mes données très facilement, mais ce n'est pas obligatoire. pandas (que je ne connais que de nom) pourrait-il m'être utile ?

Merci !

+0 -0

Pandas et le format hdf est adapté au stockage de données numeriques. J'ai perso des fichiers de plusieurs dizaines de giga sans que ça ne pose de soucis.

Pour ce genre de chose c'est vraiment pratique parce que les fonctions de calculs sont déjà implémentés et accessible. Note aussi qu'avec pandas tu peux faire le meme genre de sélections qu'en SQL (groupby, select, etc.)

Au niveau de l'identification des résultats, existe-t-il une manière particulière de faire ? Par exemple, comment dire que ce tableau est l'ensemble des coefficients de l'ACF de la production de la vache X, de aaaa-mm-dd à a'a'a'a'-m'm'-d'd', lissée Y fois ?

Suis-je obligé d'implémenter mon propre système d'indexation ?

Edit : je pourrais, dans le champ source de ma table ACF, mettre un dictionnaire du genre :

1
2
3
4
5
{
    'table': table, 
    'colonneX': valeur_colonne_X, 
    'colonneY': valeur_colonne_Y
}

Par exemple :

1
2
3
4
5
6
7
8
9
{
    'table': 'DifferencedData', 
    'degree': 1
}

{
    'table': 'SmoothedData', 
    'step': 2
}

Mais :

  • Comment je rentre un dictionnaire dans ma table ?
  • C'est sale, non ?
+0 -0

Je pars de cette table, remplie :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE IF NOT EXISTS CrudeData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    date DATE NOT NULL,
    prod REAL NOT NULL, -- L
    cons REAL NOT NULL, -- kg
    lact INTEGER NOT NULL,
    day INTEGER NOT NULL,
    UNIQUE (cow, date)
);

Puis, par exemple, je sélectionne les productions de la vache 1 :

1
SELECT prod FROM CrudeData WHERE cow = 1 ORDER BY date

Je les différencie une fois. Il me faut maintenant les stocker quelque part, que ce soit en base de données ou dans une variable Python (via pandas par exemple), afin de pouvoir utiliser ce résultat par la suite. En l'occurrence, une production différenciée ($x_{t} - x_{t-1}$) est caractérisée par la production d'origine ($x_{t}$) ainsi que le nombre de différenciations (ici, $1$). J'ai donc stocké les résultats dans cette table :

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS DifferencedData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    source INTEGER NOT NULL, -- Production de départ
    prod REAL NOT NULL, -- Production différenciée
    degree INTEGER, -- Nombre de différenciations
    FOREIGN KEY(source) REFERENCES CrudeData(id),
    UNIQUE (source, degree)
);

Sauf que maintenant je veux faire des opérations sur ces productions différenciées. Pour faire simple, mettons que je veux calculer l'ACF de toutes les productions différencées une fois de la vache 1. je fais donc :

1
2
3
4
5
6
7
data = query(
    'SELECT DifferencedData.prod FROM DifferencedData '
    'INNER JOIN CrudeData ON DifferencedData.source = CrudeData.id '
    'WHERE DifferencedData.degree = 1 AND CrudeData.cow = 1 '
    'ORDER BY CrudeData.date'
)
acf = sm.acf(data)

Maintenant, il faut que je stocke acf dans la base (ou ailleurs, mais pour l'instant c'est dans la base). Bon ben, acf est caractérisée par la vache (1), le degré de différenciation (1) et sa valeur. On pourrait en déduire la table :

1
2
3
4
5
6
7
CREATE TABLE IF NOT EXISTS ACF(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    degree INTEGER,
    coefficients BLOB,
    UNIQUE (cow, degree)
);

Sauf que si je souhaite à présent calculer l'ACF de donnée lissées, et non différenciées, il va me falloir une autre table :

1
2
3
4
5
6
7
CREATE TABLE IF NOT EXISTS ACF_SmoothedData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    step INTEGER,
    coefficients BLOB,
    UNIQUE (cow, step)
);

Certes, je n'ai changé que "degree" en "step", mais c'est parce que lissage et différenciation ne sont caractérisés que par un seul paramètre.

Comme je ne vais pas créer une table par couple (données d'origine, type d'analyse), maisp plutôt une table par type d'analyse, il me faut un moyen d'identifier les données d'origine.

Le problème est-il plus clair ?

+0 -0

L'objectif oui, mais je ne vois pas quel est le soucis avec une table du type (données d'origine, type d'analyse).

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS ACF(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    degree INTEGER,
    coefficients BLOB,
    type_acf INTEGER, -- ou une foreign key
    UNIQUE (cow, degree)
);

EDIT : si, ça y est, j'ai compris :p

Si dans les opérations que tu veux rajouter, le schéma de la table doit changer, soit tu gardes mysql en recréant un schéma de table pour chaque type d'opération, soit tu peux passer à des tables type NoSQL comme MongoDB qui sont destinées à des bases de données à schéma variable, en organisant tes données autrement.

+0 -0

Le motif général semble être : type d'analyse + données brutes d'entrée + liste des analyses précédentes avec leurs paramètres + résultat.

Le problème, c'est que (liste des analyses précédentes avec leurs paramètres) n'a pas de structure prédéfinie. Je pourrais inventer des conventions pour en rendre comptre sous forme d'une chaîne :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
CREATE TABLE IF NOT EXISTS CrudeData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cow INTEGER NOT NULL,
    date DATE NOT NULL,
    prod REAL NOT NULL, -- L
    cons REAL NOT NULL, -- kg
    lact INTEGER NOT NULL,
    day INTEGER NOT NULL,
    UNIQUE (cow, date)
);

CREATE TABLE IF NOT EXISTS AnalyzedData(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    slug VARCHAR NOT NULL,
    params BLOB,
    data BLOB,
    UNIQUE (slug)
);

Avec par exemple des lignes comme ça :

1
2
3
4
INSERT INTO AnalyzedData VALUES(NULL, 'cow=1;start=aaaa-mm-dd;end=AAAA-MM-DD;difference=1', [...], [...])
INSERT INTO AnalyzedData VALUES(NULL, 'cow=1;start=aaaa-mm-dd;end=AAAA-MM-DD;difference=2', [...], [...])
INSERT INTO AnalyzedData VALUES(NULL, 'cow=1;start=aaaa-mm-dd;end=AAAA-MM-DD;difference=1;acf', [...], [...])
INSERT INTO AnalyzedData VALUES(NULL, 'cow=1;start=aaaa-mm-dd;end=AAAA-MM-DD;smooth=2;difference=1;pacf', [...], [...])

Seulement, ça ne va pas être pratique pour faire de la recherche.

Mais du coup, MongoDB est une piste intéressante. Reste à déterminer le schéma à adopter…

Edit : je pourrais avoir un schéma comme ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    'cow-1': [
        {
            'type': 'difference',
            'degree': 1,
            'data': [...],
            'children': [
                {
                    'type': 'acf',
                    'data': [],
                    'children': []
                }
            ]
        },
        {
            'type': 'smoothing',
            'step': 2,
            'data': [...],
            'children': [
                {
                    'type': 'difference',
                    'degree': 1,
                    'data': [...],
                    'children': []
                },
            ]
        }
    ]
}

Par contre, il n'y a pas ici de moyen d'identifier des sous-ensembles de données. Par exemple, la fonction ACF des productions de telle vache uniquement de telle lactation. Mais ce n'est pas très important dans mon cas, même si la question est intéressante.

Les données brutes seraient stockées en base SQL.

+0 -0

Merci Kje, je regarde ça. ^^

@alexandre : je ne comprends pas ce que tu veux dire. Une analyse est attachée à une vache (idéalement, à une partie de la production d'une vache, mais commençons simplement et travaillons sur toute la production). Les données brutes seront stockées en base SQL, comme c'est le cas actuellement dans la table CrudeData.

+0 -0

Pour une vache+donnée :

1
2
3
4
5
6
7
8
9
{
    id : int,
    cow_id : int,
    date : {
        start : Date,
        end : Date
    },
    data : []
}

Et pour une analyse

1
2
3
4
5
6
7
8
9
{
    id : int,
    data_id : [],
    type : int, 
    data : [],
    settings : {
        ...
    }
}

En organisant encore un peu différement tu peux même imaginer sauvegarder des calculs sur plusieurs vaches, plusieurs ensembles de données, et définir les paramètres utilisés pour ça, et pour les types, tu peux définir les types-differential, et types-acf-differential quelque part.

Je t'avoue que je n'y connais pas grand chose à ce que tu fais, mais en organisant différemment tu as surement moyen d'obtenir les opérations que tu souhaites.

+0 -0

Le champ data du premier objet correspond à quoi ?

Pourquoi distingues-tu (vache + données) et analyse ? Après tout, les premières sont une analyse dont le processeur est la fonction identité. ^^

Pourquoi n'imbriques-tu pas les analyses comme je le suggère ci-dessus ? Ca m'éviterait d'avoir des types composés du genre "acf-differential".

+0 -0

Le champ data du premier objet correspond à quoi ?

Pourquoi distingues-tu (vache + données) et analyse ? Après tout, les premières sont une analyse dont le processeur est la fonction identité. ^^

C'est les données enregistrées sans traitement, mais effectivement tu peux mettre un type "identité".

Pourquoi n'imbriques-tu pas les analyses comme je le suggère ci-dessus ? Ca m'éviterait d'avoir des types composés du genre "acf-differential".

Vayel

Tu fais bien comme tu veux dans tous les cas, je crois bien :p Mais pour revenir au sujet en lui même, si tu n'as pas besoin de faire des recherches spécifiques sur les paramètres d'analyse, mais seulement sur l'analyse en elle même, tu peux mettre les paramètres dans un champ en json et récupérer ces champs après sélection pour récupérer l'id de ce qui t'intéresse (surtout si tu n'as pas 1000 versions différentes avec des paramètres différents), avec un *SQL. Il y a aussi PostgreSql qui supporte json (je ne sais pas exactement comment). Et ça te permettra de garder toutes les opérations auxquelles tu peux penser.

Ici un document qui fait un état des lieux assez succinct du monde des BDD : http://www.leonardmeyer.com/wp-content/uploads/2014/06/avenirDuNoSQL.pdf . Ca vaut pas une bonne préparation d'oraux de concours pour passer le temps, mais ça peut être utile pour occuper des vacances. ;D

Je pense que je vais partir sur la représentation JSON. Par contre, utiliser MongoDB ne sera pas particulièrement utile vu que je n'ai pas vraiment besoin de stocker de manière persistente les résultats des analyses.

Pour "simuler" un MongoDB, i.e. pour avoir un dictionnaire sur lequel je peux faire des recherches et du tri facilement, je pense utiliser pandas. Y a-t-il une classe particulière qui corresponderait à mon besoin ? La classe DataFrame semble convenir.

+0 -0

Ouep, il m'a semblé voir ça… :(

Ce qu'il me faudrait, c'est une MongoDB intégrée à Python. Autrement dit, une lib me permettant de faire sur les dictionnaires les opérations (les plus courantes) que permet de faire MongoDB sur les objets JSON stockés en base.

Sinon je passe par MongoDB + PyMongo, mais c'est un peu bête vu que je n'ai pas particulièrement besoin de stockage persistent.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte