2017-04-24 3 views
3

Je voudrais écrire une fonction (de préférence dans R, mais d'autres langues sont les bienvenus), qui identifierait les relations entre les colonnes (limité à des additions/soustractions) dans un jeu de données. Une application pratique de ce serait de l'exécuter sur grands ensembles de données financières à plusieurs colonnes, où certaines des colonnes sont des sous-totaux d'autres colonnes - et identifier les sous-totaux.Comment identifier les colonnes qui sont des sommes d'autres colonnes dans un jeu de données

Idéalement, je voudrais permettre à des petits écarts - par exemple pour permettre des problèmes d'arrondi conduisant à des colonnes ne totalisant pas exactement 100%.

J'ai trouvé le question qui inclut une solution impliquant des matrices et des rangs, mais je ne suis pas sûr s'il y a un moyen d'incorporer la capacité de gérer le bruit dans les données découlant des problèmes d'arrondi.

À titre d'exemple:

d = data.frame(a=c(10.12, 20.02, 30.08, 20.19), b=c(12.12, 20.45, 20.52, 16.72), c=c(11, 123.25, 20.67, 20.78)) 
d$d = d$a + d$b 
d$e = d$d + d$c 
> d 
     a  b  c  d  e 
1 10.12 12.12 11.00 22.24 33.24 
2 20.02 20.45 123.25 40.47 163.72 
3 30.08 20.52 20.67 50.60 71.27 
4 20.19 16.72 20.78 36.91 57.69 

magic_function(d) 
[1] "d$d = d$a + d$b" 
[2] "d$e = d$d + d$c" # or "d$e = d$a + d$b + d$c" (first option preferred) 

La solution dans la question liée fonctionne bien jusqu'à ce que je présente le bruit dans l'équation. par exemple. d$d[[4]] = d$d[[4]] + 0.01 - alors ça ne marche plus du tout. Ma question est donc:

  1. Existe-t-il d'autres méthodes pour identifier les relations entre les colonnes (surtout si elles sont limitées à de simples addition/soustraction)
  2. Est l'une des méthodes capables de répondre à la données imparfaite problème de qualité ou ai-je besoin de construire des fonctionnalités supplémentaires pour elle (par exemple autour des données avant de l'exécuter par l'algorithme d'identification de rang).
+0

Une réponse partielle est d'utiliser 'lm'. Cela devrait au moins renvoyer les variables qui sont des combinaisons linéaires d'autres comme NA 'summary (lm (rnorm (4) ~., Data = d))' par exemple. – lmo

+0

@lmo corrigez-moi si je me trompe, mais lm nécessite de connaître la (les) variable (s) dépendante (s) - dans ce cas, je ne sais pas ce qui dépend et ce qui est indépendant .. – Aurimas

+0

Cela est vrai en théorie et peut-être suis-je abuser de la fonction, mais notez que j'ai ajouté un échantillon aléatoire de la distribution normale standard en tant que variable dépendante. Maintenant, les covariables (les variables "indépendantes") sont évaluées quant à leur colinéarité. Si trop proche, alors 'lm' renvoie un NA. Je crois (sur la base d'un post SO précédent) que les covariables sont sélectionnées dans l'ordre d'une plus grande «indépendance», de sorte que celles qui sont les plus colinéaires sont exclues des résultats de la régression. – lmo

Répondre

0

Voici une idée qui fonctionnera si vous avez seulement besoin de vérifier si une colonne est le résultat de la somme de DEUX autres. Cela vous permet également d'ajouter du bruit. Nous créons d'abord un cadre de données en ajoutant toutes les combinaisons d'ensemble de données originales. Nous soustrayons ensuite chaque colonne de l'ensemble de données avec la trame de données créée. Si toutes les valeurs sont 0, cela signifie qu'elles correspondent. En utilisant colSums(i < 0.01) == nrow(i)), nous sommes en mesure d'ajouter le bruit requis.

d2 <- setNames(data.frame(combn(1:ncol(d), 2, function(i) rowSums(d[i]))), 
       combn(names(d), 2, function(j)paste(j, collapse = ' + '))) 

l1 <- lapply(d, function(i) sapply(d2, function(j) Map(function(x, y)abs(x - y), i, j))) 

lapply(l1, function(i) names(which(colSums(i < 0.01) == nrow(i)))) 

#$a 
#character(0) 

#$b 
#character(0) 

#$c 
#character(0) 

#$d 
#[1] "a + b" 

#$e 
#[1] "c + d" 

Ou faire une fonction avec noise comme argument d'entrée,

f1 <- function(df, noise){ 
    d2 <- setNames(data.frame(combn(1:ncol(df), 2, function(i) rowSums(df[i]))), 
       combn(names(df), 2, function(j)paste(j, collapse = ' + '))) 
    l1 <- lapply(df, function(i) sapply(d2, function(j) 
         Map(function(x, y)abs(x - y), i, j))) 
    Filter(length, lapply(l1, function(i) 
       names(which(colSums(i < noise) == nrow(i))))) 
} 

f1(d, 0.01) 
#$d 
#[1] "a + b" 

#$e 
#[1] "c + d" 

Si nous voulons le rendre plus flexible, alors nous pouvons ajouter un autre argument pour prendre le numéro de combinaison (de colonnes), à savoir

f1 <- function(df, n, noise){ 
    d2 <- setNames(data.frame(combn(1:ncol(df), n, function(i) rowSums(df[i]))), 
       combn(names(df), n, function(j)paste(j, collapse = ' + '))) 
    l1 <- lapply(df, function(i) sapply(d2, function(j) 
         Map(function(x, y)abs(x - y), i, j))) 
    Filter(length, lapply(l1, function(i) 
       names(which(colSums(i < noise) == nrow(i))))) 
} 

sapply(2:3, function(i) f1(d, i, 0.01)) 
#[[1]] 
#[[1]]$d 
#[1] "a + b" 

#[[1]]$e 
#[1] "c + d" 

#[[2]] 
#[[2]]$e 
#[1] "a + b + c" 
+0

Merci. Malheureusement, les cas pratiques impliqueront presque toujours plus de 2 colonnes. Je suppose que cette approche n'est pas très évolutive pour supporter au moins, disons, 10-20 colonnes? – Aurimas

+0

Si vous voulez dire que 1 colonne pourrait être le produit de la somme de 10 ou 20 autres alors non, cela ne fonctionnera pas. Vous devrez changer le paramètre 'combn' de 2 à n'importe quel nombre de colonnes que vous voulez – Sotos

+0

@Aurimas J'ai édité la fonction pour la rendre plus flexible bien que je soupçonne qu'elle ne fonctionnerait pas très bien en termes d'efficacité – Sotos

0

Si vous accueillez les sommes à être seulement pour consécutives colonnes, et pour les valeurs précédentes uniquement, le calcul eff ort pour cela est probablement traitable pour 10-20 colonnes. Cette procédure vérifie si la colonne est égale à la somme des colonnes consécutives précédentes, avec une certaine marge d'erreur:

d <- data.frame(a=c(10.12, 20.02, 30.08, 20.19), 
       b=c(12.12, 20.45, 20.52, 16.72), 
       c=c(11, 123.25, 20.67, 20.78)); 
d$d <- round(d$a + d$b + runif(4,0,0.04),2); 
d$e <- round(d$d + d$c + runif(4,0,0.04),2); 

## Assumptions: 
## * sum columns relate to previous values only 
## * sum columns relate to consecutive columns 

sumColumns <- NULL; 
allowedError <- 0.05; 
for(col in 3:ncol(d)){ 
    for(subStart in 1:(col-2)){ 
     for(subEnd in (subStart+1):(col-1)){ 
      if(all(abs(d[,col] - rowSums(d[,subStart:subEnd, drop=FALSE])) < 
        allowedError)){ 
       cat(sprintf("Column %d is a sum of columns %d-%d\n", 
          col, subStart, subEnd)); 
       sumColumns[col] <- TRUE; 
      } 
     } 
    } 
} 

Sortie:

Column 4 is a sum of columns 1-2 
Column 5 is a sum of columns 3-4 

Cela pourrait être modifié pour permettre des colonnes consécutives ensemble avec un nombre quelconque de colonnes de somme tout en conservant la traitabilité (en supposant que le nombre de colonnes de somme soit maintenu bas). Cette modification n'est pas complètement triviale et est laissée au lecteur en tant qu'exercice.

+0

Merci - dans les jeux de données réels, la limitation de «consécution» signifiera que les sous-totaux de «second ordre» ne seront pas identifiés (c.-à-d. = f, et g = c + f), mais je vois où cela peut encore être utile! – Aurimas

+1

C'est là que l'inclusion de colonnes de somme aide. Si vous autorisez des colonnes de somme non consécutives ainsi que des colonnes de non-somme consécutives, les totaux de deuxième ordre seront également inclus. – gringer