2
votes

Grouper et Sum CSV avec un nombre inconnu de colonnes

Je me demande si quelqu'un pourrait m'aider. Le problème est que j'essaye d'importer, de regrouper, de faire la somme et d'exporter un CSV. Le problème est que mon CSV a un nombre inconnu de colonnes du format suivant.

GroupA, GroupB, GroupC, ValueA, ValueB, ValueC, ValueD ...

GroupA, B et C sont constants et les champs que je souhaite regrouper - je connais à l'avance les noms de ces champs. Le problème est qu'il y a un nombre inconnu de colonnes Value - que je veux toutes additionner (et je ne connais pas les noms à l'avance.)

Je suis à l'aise pour faire fonctionner ce code si je sais le nom des champs de valeur et ont un nombre fixe de champs de valeur. Mais j'ai du mal à obtenir du code pour les noms et le nombre de colonnes inconnus.

GroupA, GroupB, Value A  
Sam, Apple, 30  
Sam, Orange, 50  
Ian, Apple, 15

Exemple de données d'entrée -

GroupA, GroupB, Value A  
Sam, Apple, 10  
Sam, Apple, 20  
Sam, Orange, 50  
Ian, Apple, 15  

Données de sortie -

$csvImport = import-csv 'C:\input.csv'

$csvGrouped = $csvImport | Group-Object -property GroupA,GroupB,GroupC

$csvGroupedFinal = $csvGrouped | Select-Object @{Name = 'GroupA';Expression={$_.Values[0]}},
        @{Name = 'GroupB';Expression={$_.Values[1]}},
        @{Name = 'GroupC';Expression={$_.Values[2]}},                      
        @{Name = 'ValueA' ;Expression={
                ($_.Group|Measure-Object 'ValueA' -Sum).Sum
        }}

$csvGroupedFinal | Export-Csv 'C:\output.csv' -NoTypeInformation


4 commentaires

Vous devez fournir des exemples de données d'entrée et de sortie attendue. Je ne sais pas si vous voulez additionner les colonnes par ligne ou les totaux de toutes les lignes par colonne.


Les totaux de toutes les lignes par colonne ont donné des exemples de données. Je vous remercie!


Devez-vous utiliser PowerShell? Cela peut être facilement fait en python.


Malheureusement, je ne peux pas utiliser Python, je suis dans un environnement de travail restreint.


3 Réponses :


1
votes

Après l'importation, ce script divise les propriétés (colonnes) en Groupes / Valeurs

  • L'ordre des entrées est maintenu avec un Select-Object final


Groups      ValueA ValueB ValueC
------      ------ ------ ------
Sam, Apple      30      4     20
Sam, Orange     50      4      5
Ian, Apple      15      3      3

Avec cet exemple de fichier d'entrée

Groups      ValueA ValueB
------      ------ ------
Sam, Apple      30     40
Sam, Orange     50     75
Ian, Apple      15     20

Exemple de sortie pour n'importe quel nombre de groupes et de valeurs p >

GroupA GroupB ValueA ValueB
------ ------ ------ ------
Sam    Apple  10     15
Sam    Apple  20     25
Sam    Orange 50     75
Ian    Apple  15     20

Sans aucune modification de code, il traite également les données de Hassans.

## Q:\Test\2019\01\17\SO_54237887.ps1

$csvImport = Import-Csv '.\input.csv'

$Cols = ($csvImport[0].psobject.Properties).Name

# get list of group columns by name and wildcard
$GroupCols = $Cols | Where-Object {$_  -like 'Group*'}

# a different approach would be to select a number of leading columns
# $GroupCols = $Cols[0..1]

$ValueCols = $Cols | Where-Object {$_ -notin $GroupCols}
$OutCols = ,'Groups' + $ValueCols

$csvGrouped = $csvImport | Group-Object $GroupCols | ForEach-Object{
    $Props = @{Groups=$_.Name}
    ForEach ($ValCol in $ValueCols){
        $Props.Add($ValCol,($_.Group|Measure-Object $ValCol -Sum).Sum)
    }
    [PSCustomObject]$Props
}

$csvGrouped | Select-Object $OutCols


6 commentaires

Merci pour votre réponse. C'est une boucle de plusieurs valeurs (d'un nombre inconnu) avec lesquelles je lutte, si cela ne vous dérange pas de le démontrer.


Désolé. J'ai modifié votre réponse au lieu de ma propre réponse. Espérons que l'examen par les pairs voit cette erreur et annule la modification. Vous pouvez également rajouter votre réponse.


@SamDolbear crée maintenant dynamiquement les propriétés de [PSCustomObject]


Merci pour la modification, ressemble exactement à ce dont j'ai besoin!


Donc, un vote positif ou une réponse partagée ou une réponse de changement pour ce qui précède?


@JosefZ Réécrit le script pour utiliser la méthode initiale .PSObject.Properties qui ne classe PAS les propriétés par ordre alphabétique. Au lieu de vous fier au Groupe faire partie des colonnes, vous pouvez maintenant utiliser alternativement les premières n colonnes pour le regroupement.



0
votes

script1.ps1

PS D:\coding> .\script1.ps1

GroupA GroupB Sum
------ ------ ---
Sam    Apple   54
Sam    Orange  59
Ian    Apple   21

input.csv

GroupA, GroupB, ValueA, ValueB, ValueC
Sam, Apple, 10, 1, 10
Sam, Apple, 20, 3, 10
Sam, Orange, 50, 4, 5
Ian, Apple, 15, 3, 3

OUTPUT

Import-Csv 'input.csv' | `
Group-Object -Property GroupA,GroupB | `
% {$b=$_.name -split ', ';$c=($_.group | `
Measure-Object -Property Value* -Sum).Sum;
[PScustomobject]@{GroupA=$b[0];
GroupB=$b[1];
Sum=($c | Measure-Object -Sum).Sum }}

p>


2 commentaires

Salut, merci pour votre réponse. Malheureusement, cela fait la même chose que le code de ma question, mais ne fonctionnerait pas si je veux étendre cela pour un nombre inconnu de `` valeurs '' (valeur A, valeur B valeur C ....) - ce qui devrait être déterminé dynamiquement.


@SamDolbear Voir ma réponse modifiée ci-dessus. Différent de la réponse acceptée mais peut-être utile pour le moment ou pour une autre fois ou peut-être par quelqu'un d'autre.



1
votes

Le script suivant devrait fonctionner. Faites attention à la variable $ FixedNames :

$csvImport = @"
Group A,Group B,Value A,Value B
sam,apple,10,1
sam,apple,20,
sam,orange,50,5
ian,apple,15,51
"@ | ConvertFrom-Csv

Testé de la même façon pour

$csvImport = @"
Group A,Group B,Value A
sam,apple,10
sam,apple,20
sam,orange,50
ian,apple,15
"@ | ConvertFrom-Csv

$FixedNames  = @('Group A', 'Group B', 'Group C')
# $aux         = ($csvImport|Get-Member -MemberType NoteProperty).Name  ### sorted (wrong)
$aux         = ($csvImport[0].psobject.Properties).Name                 ### not sorted
$auxGrpNames = @( $aux     | Where-Object {$_    -in $FixedNames})
$auxValNames = @( $aux     | Where-Object {$_ -notin $FixedNames})
$csvGrouped  = $csvImport  | Group-Object -property $auxGrpNames
$csvGroupedFinal = $csvGrouped | 
    ForEach-Object {
        ($_.Name.Replace(', ',','), (($_.Group |
            Measure-Object -Property $auxValNames -Sum
                ).Sum -join ',')) -join ','
    } | ConvertFrom-Csv -Header $aux
$csvGroupedFinal

ainsi que pour plus données complexes de l'en-tête Groupe A, Groupe B, Groupe C, Valeur A, Valeur B .

Modifier mis à jour selon le Commentaire de LotPings bénéfique . p>


3 commentaires

Excellent merci! On dirait ce dont j'ai besoin! Merci encore! Testera à son retour au travail.


@SamDolbear Malheureusement, la solution dépend des noms de colonnes dans le CSV d'origine: le tableau Get-Member -MemberType NoteProperty semble être trié par la propriété Name . Par conséquent, mieux vaut obtenir les variables $ aux , $ auxGrpNames et $ auxValNames d'une autre manière (par exemple, en analysant la ligne d'en-tête)! Astuce: lisez l'en-tête d'une variable en utilisant Get-Content , divisez-le en la variable $ aux .


Vous pouvez utiliser la méthode non triée $ aux = ($ csvImport [0] .psobject.Properties) .Name pour obtenir l'en-tête. Et d'avoir les trois premiers pour trier $ auxGrpNames = $ aux [0..2]