Skip to content

PivotTable Method

The pivotTable() method creates a two-dimensional pivot table by organizing data across row and column dimensions, with values aggregated using the specified operation. The result is a nested Map structure representing the pivot table.

Basic Syntax

typescript
pivotTable<R extends keyof T, C extends keyof T, V extends keyof T>(
  rows: R,
  cols: C,
  values: V,
  aggregation: 'sum' | 'avg' | 'count'
): Map<T[R], Map<T[C], number>>

Examples

Basic Usage

typescript
import { collect } from 'ts-collect'

const sales = collect([
  { product: 'Laptop', region: 'North', revenue: 1000 },
  { product: 'Laptop', region: 'South', revenue: 1200 },
  { product: 'Mouse', region: 'North', revenue: 100 }
])

const pivot = sales.pivotTable(
  'product',    // Rows
  'region',     // Columns
  'revenue',    // Values
  'sum'         // Aggregation
)

// Results in:
// Laptop | North: 1000 | South: 1200
// Mouse  | North: 100  | South: 0

Working with Complex Data

typescript
interface SalesData {
  category: string
  salesPerson: string
  quarter: 'Q1' | 'Q2' | 'Q3' | 'Q4'
  units: number
  revenue: number
}

const salesData = collect<SalesData>([
  {
    category: 'Electronics',
    salesPerson: 'Chris',
    quarter: 'Q1',
    units: 10,
    revenue: 10000
  },
  {
    category: 'Electronics',
    salesPerson: 'Avery',
    quarter: 'Q1',
    units: 15,
    revenue: 15000
  }
])

// Sales by Category and Quarter
const quarterlyRevenue = salesData.pivotTable(
  'category',
  'quarter',
  'revenue',
  'sum'
)

// Sales Performance by Person and Category
const salesPerformance = salesData.pivotTable(
  'salesPerson',
  'category',
  'units',
  'sum'
)

Real-world Example: E-commerce Sales Analysis

typescript
interface OrderData {
  productId: string
  category: string
  region: string
  channel: string
  revenue: number
  quantity: number
  date: Date
}

class SalesAnalyzer {
  private orders: Collection<OrderData>

  constructor(orders: OrderData[]) {
    this.orders = collect(orders)
  }

  getCategoryPerformanceByRegion() {
    return this.orders.pivotTable(
      'category',
      'region',
      'revenue',
      'sum'
    )
  }

  getChannelPerformance() {
    const pivot = this.orders.pivotTable(
      'channel',
      'category',
      'revenue',
      'sum'
    )

    return Array.from(pivot.entries()).map(([channel, categoryData]) => ({
      channel,
      categories: Object.fromEntries(categoryData),
      totalRevenue: Array.from(categoryData.values())
        .reduce((sum, value) => sum + value, 0)
    }))
  }

  getQuarterlySalesByCategory() {
    const withQuarter = this.orders.map(order => ({
      ...order,
      quarter: `Q${Math.floor(order.date.getMonth() / 3) + 1}`
    }))

    return collect(withQuarter).pivotTable(
      'category',
      'quarter',
      'revenue',
      'avg'
    )
  }

  formatPivotTable(
    pivot: Map<string, Map<string, number>>,
    formatter: (value: number) => string = n => n.toString()
  ) {
    const rows: string[][] = []
    const columns = new Set<string>()

    // Get all column headers
    pivot.forEach((columnData) => {
      columnData.forEach((_, col) => columns.add(col))
    })

    // Add header row
    const headers = ['', ...Array.from(columns)]
    rows.push(headers)

    // Add data rows
    pivot.forEach((columnData, rowHeader) => {
      const row = [rowHeader]
      columns.forEach(col => {
        const value = columnData.get(col) || 0
        row.push(formatter(value))
      })
      rows.push(row)
    })

    return rows
  }
}

// Usage
const analyzer = new SalesAnalyzer([
  {
    productId: 'P1',
    category: 'Electronics',
    region: 'North',
    channel: 'Online',
    revenue: 1000,
    quantity: 1,
    date: new Date('2024-01-15')
  },
  {
    productId: 'P2',
    category: 'Electronics',
    region: 'South',
    channel: 'Retail',
    revenue: 1500,
    quantity: 2,
    date: new Date('2024-02-15')
  }
])

const categoryRevenue = analyzer.getCategoryPerformanceByRegion()
const formattedTable = analyzer.formatPivotTable(
  categoryRevenue,
  value => `$${value.toLocaleString()}`
)

Type Safety

typescript
interface Data {
  department: string
  employee: string
  sales: number
  rating: string  // non-numeric field
}

const data = collect<Data>([
  { department: 'Sales', employee: 'Chris', sales: 1000, rating: 'A' },
  { department: 'Sales', employee: 'Avery', sales: 1500, rating: 'B' }
])

// Type-safe pivot table creation
const salesPivot = data.pivotTable(
  'department',
  'employee',
  'sales',     // Must be numeric for 'sum' or 'avg'
  'sum'
)

// TypeScript enforces numeric values for sum/avg
// data.pivotTable('department', 'employee', 'rating', 'sum')  // ✗ TypeScript error

Return Value

  • Returns a nested Map structure:
    • Outer Map: Row values to inner Maps
    • Inner Maps: Column values to aggregated numbers
  • Missing combinations get:
    • 0 for 'sum'
    • 0 for 'count'
    • undefined for 'avg'
  • Maintains type safety
  • Preserves field types
  • Enforces numeric values for aggregation

Common Use Cases

1. Sales Analysis

  • Region performance
  • Product categories
  • Sales channels
  • Time periods
  • Team performance

2. Inventory Analysis

  • Stock by location
  • Category distribution
  • Supplier analysis
  • Seasonal patterns
  • Movement rates

3. Performance Metrics

  • Employee metrics
  • Department goals
  • Regional targets
  • Time comparisons
  • Success rates

4. Customer Analysis

  • Segment behavior
  • Geographic trends
  • Channel preferences
  • Product affinities
  • Time patterns

5. Financial Analysis

  • Revenue breakdowns
  • Cost centers
  • Budget analysis
  • Profit margins
  • Investment returns

Released under the MIT License.