GPandas

Pivot and Melt

Reshape DataFrames between wide and long formats using pivot tables and melt operations

Learn how to reshape DataFrames using pivot tables and melt operations in GPandas, enabling powerful data analysis workflows.

Overview

GPandas provides two complementary reshaping operations:

OperationMethodDescription
Pivot TablePivotTable()Aggregate and reshape data into a spreadsheet-style pivot table
MeltMelt()Unpivot data from wide format to long format

PivotTable

Creates a spreadsheet-style pivot table that groups data by index columns, spreads values across new columns, and applies aggregation functions.

Function Signature

func (df *DataFrame) PivotTable(opts PivotTableOptions) (*DataFrame, error)

PivotTableOptions

FieldTypeDescriptionRequired
Index[]stringColumn(s) for row groupingYes
ColumnsstringColumn whose unique values become new column headersYes
Values[]stringColumn(s) to aggregateYes
AggFuncAggFuncAggregation function to applyNo (default: AggMean)
FillValueanyValue for missing combinationsNo (default: nil)

Aggregation Functions

ConstantDescription
AggSumSum of values
AggMeanMean of values (default)
AggCountCount of non-null values
AggMinMinimum value
AggMaxMaximum value

Sample Data

All pivot examples use this sales DataFrame:

Sales DataFrame

RegionQuarterProductSales
NorthQ1Widgets100
NorthQ1Gadgets150
NorthQ2Widgets120
SouthQ1Widgets90
SouthQ2Widgets110
SouthQ2Gadgets130

Setup Code

package main

import (
    "fmt"
    "log"

    "github.com/apoplexi24/gpandas"
    "github.com/apoplexi24/gpandas/dataframe"
)

func main() {
    gp := gpandas.GoPandas{}
    
    // Create sales DataFrame
    df, _ := gp.DataFrame(
        []string{"Region", "Quarter", "Product", "Sales"},
        []gpandas.Column{
            {"North", "North", "North", "South", "South", "South"},
            {"Q1", "Q1", "Q2", "Q1", "Q2", "Q2"},
            {"Widgets", "Gadgets", "Widgets", "Widgets", "Widgets", "Gadgets"},
            {100.0, 150.0, 120.0, 90.0, 110.0, 130.0},
        },
        map[string]any{
            "Region":  gpandas.StringCol{},
            "Quarter": gpandas.StringCol{},
            "Product": gpandas.StringCol{},
            "Sales":   gpandas.FloatCol{},
        },
    )
    
    // Examples follow...
}

PivotTable with Sum

Create a pivot table showing total sales by region and quarter:

Example

pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Region"},
    Columns: "Quarter",
    Values:  []string{"Sales"},
    AggFunc: dataframe.AggSum,
})
if err != nil {
    log.Fatalf("Pivot failed: %v", err)
}
fmt.Println(pivot.String())

Output

+--------+-----+-----+
| Region | Q1  | Q2  |
+--------+-----+-----+
| North  | 250 | 120 |
| South  | 90  | 240 |
+--------+-----+-----+
[2 rows x 3 columns]

PivotTable with Mean

Calculate average sales by region and quarter:

pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Region"},
    Columns: "Quarter",
    Values:  []string{"Sales"},
    AggFunc: dataframe.AggMean,
})
if err != nil {
    log.Fatalf("Pivot failed: %v", err)
}
fmt.Println(pivot.String())

Output

+--------+-----+-----+
| Region | Q1  | Q2  |
+--------+-----+-----+
| North  | 125 | 120 |
| South  | 90  | 120 |
+--------+-----+-----+
[2 rows x 3 columns]

PivotTable with Count

Count the number of transactions by region and quarter:

pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Region"},
    Columns: "Quarter",
    Values:  []string{"Sales"},
    AggFunc: dataframe.AggCount,
})
if err != nil {
    log.Fatalf("Pivot failed: %v", err)
}
fmt.Println(pivot.String())

Output

+--------+----+----+
| Region | Q1 | Q2 |
+--------+----+----+
| North  | 2  | 1  |
| South  | 1  | 2  |
+--------+----+----+
[2 rows x 3 columns]

PivotTable with Multiple Values

Pivot with multiple value columns creates combined column names:

// Assuming DataFrame has both Sales and Units columns
pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Region"},
    Columns: "Quarter",
    Values:  []string{"Sales", "Units"},
    AggFunc: dataframe.AggSum,
})

Output

+--------+----------+----------+----------+----------+
| Region | Sales_Q1 | Sales_Q2 | Units_Q1 | Units_Q2 |
+--------+----------+----------+----------+----------+
| North  | 250      | 120      | 10       | 5        |
| South  | 90       | 240      | 4        | 12       |
+--------+----------+----------+----------+----------+
[2 rows x 5 columns]

PivotTable with FillValue

Use FillValue to replace missing combinations instead of null:

pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:     []string{"Product"},
    Columns:   "Quarter",
    Values:    []string{"Sales"},
    AggFunc:   dataframe.AggSum,
    FillValue: 0.0,  // Use 0 instead of null for missing combinations
})
if err != nil {
    log.Fatalf("Pivot failed: %v", err)
}
fmt.Println(pivot.String())

Output

+---------+-----+-----+
| Product | Q1  | Q2  |
+---------+-----+-----+
| Gadgets | 150 | 130 |
| Widgets | 190 | 230 |
+---------+-----+-----+
[2 rows x 3 columns]

Melt

Transforms a DataFrame from wide format to long format by unpivoting columns into rows.

Function Signature

func (df *DataFrame) Melt(opts MeltOptions) (*DataFrame, error)

MeltOptions

FieldTypeDescriptionDefault
IdVars[]stringIdentifier columns to keep fixed-
ValueVars[]stringColumns to unpivotAll non-id columns
VarNamestringName for the variable column"variable"
ValueNamestringName for the value column"value"

Melt Sample Data

Wide Format DataFrame (Grades)

StudentMathScienceEnglish
Alice908588
Bob807592
Charlie959085

Setup Code

gp := gpandas.GoPandas{}

grades, _ := gp.DataFrame(
    []string{"Student", "Math", "Science", "English"},
    []gpandas.Column{
        {"Alice", "Bob", "Charlie"},
        {90.0, 80.0, 95.0},
        {85.0, 75.0, 90.0},
        {88.0, 92.0, 85.0},
    },
    map[string]any{
        "Student": gpandas.StringCol{},
        "Math":    gpandas.FloatCol{},
        "Science": gpandas.FloatCol{},
        "English": gpandas.FloatCol{},
    },
)

Basic Melt

Transform from wide to long format:

Example

melted, err := grades.Melt(dataframe.MeltOptions{
    IdVars:    []string{"Student"},
    VarName:   "Subject",
    ValueName: "Score",
})
if err != nil {
    log.Fatalf("Melt failed: %v", err)
}
fmt.Println(melted.String())

Output

+---------+---------+-------+
| Student | Subject | Score |
+---------+---------+-------+
| Alice   | Math    | 90    |
| Alice   | Science | 85    |
| Alice   | English | 88    |
| Bob     | Math    | 80    |
| Bob     | Science | 75    |
| Bob     | English | 92    |
| Charlie | Math    | 95    |
| Charlie | Science | 90    |
| Charlie | English | 85    |
+---------+---------+-------+
[9 rows x 3 columns]

Melt with Specific ValueVars

Unpivot only specific columns:

melted, err := grades.Melt(dataframe.MeltOptions{
    IdVars:    []string{"Student"},
    ValueVars: []string{"Math", "Science"},  // Exclude English
    VarName:   "Subject",
    ValueName: "Score",
})
if err != nil {
    log.Fatalf("Melt failed: %v", err)
}
fmt.Println(melted.String())

Output

+---------+---------+-------+
| Student | Subject | Score |
+---------+---------+-------+
| Alice   | Math    | 90    |
| Alice   | Science | 85    |
| Bob     | Math    | 80    |
| Bob     | Science | 75    |
| Charlie | Math    | 95    |
| Charlie | Science | 90    |
+---------+---------+-------+
[6 rows x 3 columns]

Melt Without IdVars

Melt all columns into long format:

melted, err := df.Melt(dataframe.MeltOptions{
    // No IdVars - all columns unpivoted
    VarName:   "column",
    ValueName: "data",
})

Pivot and Melt Relationship

Pivot and Melt are inverse operations:

Round-Trip Example

// Start with wide format
wideDF := grades  // Student | Math | Science | English

// Convert to long format
longDF, _ := wideDF.Melt(dataframe.MeltOptions{
    IdVars:    []string{"Student"},
    VarName:   "Subject",
    ValueName: "Score",
})

// Convert back to wide format
wideAgain, _ := longDF.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Student"},
    Columns: "Subject",
    Values:  []string{"Score"},
    AggFunc: dataframe.AggSum,
})

// wideAgain has same structure as original wideDF

Aggregation Comparison

Visual comparison of all aggregation functions:

AggFuncFormulaUse Case
AggSumΣ valuesTotal sales, counts
AggMeanΣ values / nAverages, ratings
AggCountCount non-nullTransaction counts
AggMinmin(values)Lowest price, first date
AggMaxmax(values)Highest score, peak value

Error Handling

PivotTable Errors

ErrorCauseSolution
"Index column(s) must be specified"Empty IndexProvide at least one index column
"Columns parameter must be specified"Empty ColumnsSpecify column for headers
"Values column(s) must be specified"Empty ValuesSpecify columns to aggregate
"index column 'X' not found"Invalid column nameCheck column exists

Melt Errors

ErrorCauseSolution
"id_vars column 'X' not found"Invalid IdVarsCheck column exists
"value_vars column 'X' not found"Invalid ValueVarsCheck column exists
"no columns to melt"All columns in IdVarsLeave some columns to unpivot

Error Handling Example

pivot, err := df.PivotTable(dataframe.PivotTableOptions{
    Index:   []string{"Region"},
    Columns: "Quarter",
    Values:  []string{"Sales"},
    AggFunc: dataframe.AggSum,
})
if err != nil {
    switch {
    case strings.Contains(err.Error(), "not found"):
        log.Fatal("Column doesn't exist in DataFrame")
    case strings.Contains(err.Error(), "must be specified"):
        log.Fatal("Missing required option")
    default:
        log.Fatalf("Pivot error: %v", err)
    }
}

Complete Example: Sales Analysis

package main

import (
    "fmt"
    "log"

    "github.com/apoplexi24/gpandas"
    "github.com/apoplexi24/gpandas/dataframe"
)

func main() {
    gp := gpandas.GoPandas{}
    
    // Load sales data
    sales, err := gp.Read_csv("sales_data.csv")
    if err != nil {
        log.Fatalf("Failed to load data: %v", err)
    }
    
    fmt.Println("Original Data:")
    fmt.Println(sales.String())
    
    // Create pivot table: Total sales by product and region
    pivot, err := sales.PivotTable(dataframe.PivotTableOptions{
        Index:     []string{"Product"},
        Columns:   "Region",
        Values:    []string{"Revenue"},
        AggFunc:   dataframe.AggSum,
        FillValue: 0.0,
    })
    if err != nil {
        log.Fatalf("Pivot failed: %v", err)
    }
    
    fmt.Println("\nSales by Product and Region:")
    fmt.Println(pivot.String())
    
    // Export pivot table
    _, err = pivot.ToCSV("sales_pivot.csv", ",")
    if err != nil {
        log.Printf("Export warning: %v", err)
    }
    
    // Melt the pivot table back to long format for visualization
    melted, err := pivot.Melt(dataframe.MeltOptions{
        IdVars:    []string{"Product"},
        VarName:   "Region",
        ValueName: "TotalRevenue",
    })
    if err != nil {
        log.Fatalf("Melt failed: %v", err)
    }
    
    fmt.Println("\nMelted for Visualization:")
    fmt.Println(melted.String())
}

See Also

On this page