UI Pivot Tables

Warning

The Deephaven Enterprise Engine supports Pivot Tables, but the UI Widget is not yet available. To display pivots, you must develop a custom UI with the JavaScript or Python clients.

UI Pivot Tables are designed for displaying information that is aggregated in two or more dimensions, like a rollup across both rows and columns. Pivot tables transform your data to show relationships between different dimensions, making complex data easier to analyze and interpret.

Pivot tables are particularly useful when:

  • You need to analyze data across multiple dimensions simultaneously.
  • You want to summarize large datasets into manageable insights.
  • You need to perform comparative analysis between different categories.
  • You want to create cross-tabulations for reporting purposes.

To create a Pivot Table, you must specify three core components:

  • Row by-columns: Defines the grouping for rows (vertical organization).
  • Column by-columns: Defines the grouping for columns (horizontal organization).
  • Aggregations: Defines the calculations performed on data points.

Create pivot tables

To create a Pivot Table, use these API methods:

Example: Log message analysis

In this example, we retrieve the ProcessEventLog table and create a Pivot showing the number of log messages for each Process. The rows are aggregated by Date, EffectiveUser, and ProcessInfoId. The columns are aggregated by log levels (e.g., ERROR or INFO).

import io.deephaven.api.ColumnName
import io.deephaven.pivot.PivotTable
import static io.deephaven.api.agg.Aggregation.AggCount

import io.deephaven.enterprise.acl.EdgeAclProvider
import io.deephaven.enterprise.acl.AclFilterGenerator


processEventLog=db.liveTable("DbInternal", "ProcessEventLog").where("Date >= io.deephaven.time.calendar.Calendars.calendar().minusDays(today(), 5)")
pivot=PivotTable.make(processEventLog,
        [AggCount("Count")],
        [ColumnName.of("Date"), ColumnName.of("EffectiveUser"), ColumnName.of("ProcessInfoId")],
        [ColumnName.of("Level")])
from deephaven_enterprise.pivot.pivot_table import (
    PivotTable,
    make_pivot,
)
from deephaven.agg import count_
from deephaven_enterprise.edge_acl import EdgeAclProvider
import deephaven_enterprise.acl_generator as acl_generator

process_event_log = db.live_table("DbInternal", "ProcessEventLog").where(
    "Date >= io.deephaven.time.calendar.Calendars.calendar().minusDays(today(), 5)"
)
pivot = make_pivot(
    process_event_log,
    [count_("Count")],
    ["Date", "EffectiveUser", "ProcessInfoId"],
    ["Level"],
)

Data visualization

The following table is one possible visual representation of this pivot. You can see there are a total of 33,012 log messages, of which 19,638 are INFO messages. On 2025-06-30 there were 13,619 messages, of which 5,946 were for Bob's processes and 7,673 for Alice's processes. We can see that Bob's process with the ID 658337fc-4eb4-4d3f-aef6-3c39066a59fe had 2,491 messages, of which 2,331 were INFO; 1 WARN; 158 STDOUT; and 1 STDERR.

DateEffectiveUserProcessInfoIdTotalINFOWARNERRORSTDOUTSTDERR
330121963849919105242332
2025-06-301361997478237819
2025-06-30bob5946529126494
2025-06-30bob123a2bde-8540-4523-b834-01d9d4f18fb367250311671
2025-06-30bob8bc6df68-7eef-423a-80e1-f917d59b781e5994311671
2025-06-30bob006bf090-9fd2-421a-85c8-919e5d0653e5218420261571
2025-06-30bob658337fc-4eb4-4d3f-aef6-3c39066a59fe2491233111581
2025-06-30alice767344568031325
2025-06-30alice7fc0d1af-01c8-48e4-8381-22525a9fcfe314871262613341
2025-06-30alice687f56b8-dbec-4763-9e60-bcd86302891f75258911611
2025-06-30aliceff49a554-3bff-4551-9018-fc14c14a1bbe3659349831571
2025-06-30alice70ce127e-b5b0-4584-93e1-cb7d0eabd94c14681222513201
2025-06-30alice04a282a6-a784-409c-a4e0-f450bdee737f307121251601
2025-07-011939398914171967432323
2025-07-01alice1403555154141957702317
2025-07-01alicee3346e22-9f96-40a6-a466-26df3cfd542e33116711621
2025-07-01alicea030c4d1-e470-4c08-bcf5-4b59f02bf2d114741231913311
2025-07-01alicec13e0754-0ca3-4885-9023-8ed97f46502060043611621
2025-07-01alicebd109e38-d551-434b-a661-8fdc8c78f8c614861204113241
2025-07-01alice428f705d-8484-40ab-87dc-b4bba32b9a2e4452234781661
2025-07-01alice5c70ef7b-2423-4e01-99e5-d0c8af0ad5bc415212441581
2025-07-01alice64205453-facf-4476-bca7-d2b3b50cc3a214751192713281
2025-07-01aliced7ba5baa-9f0b-475d-8e44-db22581717d33250309111571
2025-07-01alice5554cf08-d93a-4413-a62b-f2f9d6c251f1383176481581
2025-07-01alice9991450b-66e5-4104-9266-2bee3357afd515541554311661189
2025-07-01alicea0cc164c-092f-4d5f-b319-7010ea4dc0ca4332174361661
2025-07-01alice3f933375-29ac-44da-8d2f-b3109b4dee3f355148401661
2025-07-01alice81c415b9-e6c3-41df-9968-170a0aceb4ac15212043141661116
2025-07-01alicec986fc3c-01f3-4434-837c-45867f0eb0fe313124281601
2025-07-01bob5358437639736
2025-07-01bobc9ba1205-54ca-4aaa-a1a1-79aa472e098e3692011671
2025-07-01bob84eeebb7-c6a0-4f24-a0f7-c1f15ae2cf8e40824211641
2025-07-01bob07c534cb-e416-49c4-b837-20ef85b94b374202581611
2025-07-01bob172340a6-6575-4859-93b4-42b61a505f0a47230311671
2025-07-01bobd30849fd-5c25-473e-aa8e-1010c8bc9afd1962180311571
2025-07-01bob860273d2-7834-405b-9e76-de084709472d172715691571

Filters and sorts

A Pivot Table can be sorted along either the rows or columns dimension using either the by-columns or the total aggregations for that dimension. Pivot sorts work identically to rollup sorts in each dimension—each node is sorted independently, maintaining the pivot's hierarchical structure.

A Pivot Table may be filtered by row or column keys, but cannot be filtered by aggregated values. After filtering, aggregations are recomputed so that totals only include the values accepted by the filter.

If you want to filter by input columns other than the keys, you must filter the source table and re-apply the pivot.

In Groovy, sorts and filters are added to the Pivot with the apply method. The method takes two collections of SortColumns, one for the rows and another for the columns, and a list of Filters.

import io.deephaven.api.SortColumn
import io.deephaven.api.ColumnName
import io.deephaven.engine.table.impl.select.WhereFilterFactory

// Sort the rows by keys
by_key = pivot.apply([SortColumn.asc(ColumnName.of("Date")), SortColumn.asc(ColumnName.of("EffectiveUser")), SortColumn.asc(ColumnName.of("ProcessInfoId"))], [], [])

// Sort the rows and columns by count, displaying the most common values on top and to the left
by_count = pivot.apply([SortColumn.desc(ColumnName.of("Count"))], [SortColumn.desc(ColumnName.of("Count"))], [])

// Only include "ERROR" and "STDERR"
some_levels = pivot.apply([], [], [WhereFilterFactory.getExpression("Level.contains(`ERR`)")])

In Python, use sort_rows and sort_cols to sort the rows and columns of the pivot, respectively.

from deephaven.table import SortDirection

# Sort the rows by keys
by_key = pivot.sort_rows(["Date", "EffectiveUser", "ProcessInfoId"])

# Sort the rows and columns by count, displaying the most common values on top and to the left
by_count = pivot.sort_rows("Count", order=SortDirection.DESCENDING).sort_cols(
    "Count", order=SortDirection.DESCENDING
)

Use the filter method to filter on key columns.

# Only include "ERROR" and "STDERR"
some_levels = pivot_table.filter("Level.contains(`ERR`)")

Security considerations

Access Control Lists

When an ACL (Access Control List) is applied to a pivot table, it is applied when a user fetches the pivot table from the server. The ACL is applied to the source of the pivot, thus allowing the user to see the aggregated values across the permitted rows.

Example: User-specific data filtering

Extending the previous example, we add a group filter to the EffectiveUser column. This filter permits users to view only rows that have an EffectiveUser value that matches one of the groups they are a member of. Because each user is a member of a group that matches their user name, and the EffectiveUser column contains the username that a worker is running as, the effect is to display a pivot that includes only that user's processes.

def ACL = EdgeAclProvider.builder()
        .rowAcl("allusers", AclFilterGenerator.group("EffectiveUser"))
        .build()

pivot = ACL.applyTo(pivot)
ACL = (
    EdgeAclProvider.builder()
    .row_acl("allusers", acl_generator.group("EffectiveUser"))
    .build()
)

pivot = ACL.apply_to(pivot)