GPandas

Membership, Range & Top-N

Subset rows by set membership, value range, or the n largest and smallest values

Learn how to subset DataFrame rows in GPandas with four high-frequency selection helpers: Isin for set membership, Between for value ranges, and Nlargest/Nsmallest for top-n selection. Each one is a step in the same chainable, error-deferred FilterChain used by Filter and Where, so they compose freely with the filters you already use.

Overview

OperationMethodDescription
Set MembershipIsin()Keep rows whose column value appears in a set of values
RangeBetween()Keep rows whose column value falls between two bounds
Top NNlargest()Keep the n rows with the largest values, largest first
Bottom NNsmallest()Keep the n rows with the smallest values, smallest first

All four start (or extend) a FilterChain that is terminated with Result(), MustResult(), or Err().


Sample Data

All examples use this employee DataFrame:

Employees DataFrame

NameDepartmentAgeSalary
AliceEngineering3095000
BobSales2555000
CharlieEngineering35105000
DianaSales2862000
EveMarketing3272000
FrankEngineering2788000

Setup Code

package main

import (
    "fmt"
    "log"

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

func main() {
    gp := gpandas.GoPandas{}

    // Create employee DataFrame
    df, _ := gp.DataFrame(
        []string{"Name", "Department", "Age", "Salary"},
        []gpandas.Column{
            {"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"},
            {"Engineering", "Sales", "Engineering", "Sales", "Marketing", "Engineering"},
            {int64(30), int64(25), int64(35), int64(28), int64(32), int64(27)},
            {95000.0, 55000.0, 105000.0, 62000.0, 72000.0, 88000.0},
        },
        map[string]any{
            "Name":       gpandas.StringCol{},
            "Department": gpandas.StringCol{},
            "Age":        gpandas.IntCol{},
            "Salary":     gpandas.FloatCol{},
        },
    )

    // Examples follow...
}

Isin

Keeps rows whose value in a column appears in the supplied set, similar to pandas df[df["Department"].isin([...])].

Function Signature

func (df *DataFrame) Isin(column string, values []any) *FilterChain

Matching Rules

Value TypeMatching
float64 / int64 / intNumeric, across types (an int column matches float64 members and vice versa)
stringExact equality
boolExact equality
Other comparable typesExact equality

Note: The set is built once per call, so matching costs one hash lookup per row rather than a scan of values.

Basic Example

Keep employees in Engineering or Marketing:

result, err := df.Isin("Department", []any{"Engineering", "Marketing"}).Result()
if err != nil {
    log.Fatalf("Isin failed: %v", err)
}
fmt.Println(result.String())

Output

+---------+-------------+-----+--------+
| Name    | Department  | Age | Salary |
+---------+-------------+-----+--------+
| Alice   | Engineering | 30  | 95000  |
| Charlie | Engineering | 35  | 105000 |
| Eve     | Marketing   | 32  | 72000  |
| Frank   | Engineering | 27  | 88000  |
+---------+-------------+-----+--------+
[4 rows x 4 columns]

Mixed Numeric Members

Numeric members do not have to match the column's exact Go type. Here an int64 column is matched against an int, a float64, and an int64 literal:

result, err := df.Isin("Age", []any{25, 30.0, int64(35)}).Result()
if err != nil {
    log.Fatalf("Isin failed: %v", err)
}
fmt.Println(result.String())

Output

+---------+-------------+-----+--------+
| Name    | Department  | Age | Salary |
+---------+-------------+-----+--------+
| Alice   | Engineering | 30  | 95000  |
| Bob     | Sales       | 25  | 55000  |
| Charlie | Engineering | 35  | 105000 |
+---------+-------------+-----+--------+
[3 rows x 4 columns]

Note: Matching is numeric only between numeric types. A string member such as "30" never matches a numeric column.

Empty Set

An empty (or nil) set selects no rows, matching pandas where isin([]) is False everywhere:

result, _ := df.Isin("Department", []any{}).Result()
fmt.Println(result.String())

Output

+------+------------+-----+--------+
| Name | Department | Age | Salary |
+------+------------+-----+--------+
+------+------------+-----+--------+
[0 rows x 4 columns]

Between

Keeps rows whose value in a column falls between two bounds, similar to pandas df[df["Age"].between(low, high)].

Function Signature

func (df *DataFrame) Between(column string, low, high any, inclusive Inclusive) *FilterChain

Inclusive Constants

The inclusive argument selects which bounds belong to the range:

ConstantRangeKeeps rows where
InclusiveBoth[low, high]low <= value <= high
InclusiveNeither(low, high)low < value < high
InclusiveLeft[low, high)low <= value < high
InclusiveRight(low, high]low < value <= high

Note: The zero value ("") behaves as InclusiveBoth, so df.Between("Age", 27, 32, "") includes both bounds.

Comparison Rules

Bounds follow the same comparison rules as Filter: numeric bounds compare across int, int64, and float64, strings compare lexicographically, and booleans order false < true.

Basic Example

Ages from 27 through 32, both bounds included:

result, err := df.Between("Age", 27, 32, dataframe.InclusiveBoth).Result()
if err != nil {
    log.Fatalf("Between failed: %v", err)
}
fmt.Println(result.String())

Output

+-------+-------------+-----+--------+
| Name  | Department  | Age | Salary |
+-------+-------------+-----+--------+
| Alice | Engineering | 30  | 95000  |
| Diana | Sales       | 28  | 62000  |
| Eve   | Marketing   | 32  | 72000  |
| Frank | Engineering | 27  | 88000  |
+-------+-------------+-----+--------+
[4 rows x 4 columns]

Excluding Bounds

The same range with different inclusive settings selects different rows. Ages present in the data are 25, 27, 28, 30, 32, and 35:

neither, _ := df.Between("Age", 27, 32, dataframe.InclusiveNeither).Result()
left, _    := df.Between("Age", 27, 32, dataframe.InclusiveLeft).Result()
right, _   := df.Between("Age", 27, 32, dataframe.InclusiveRight).Result()

Results Compared

SettingRangeRows kept
InclusiveBoth[27, 32]Alice (30), Diana (28), Eve (32), Frank (27)
InclusiveNeither(27, 32)Alice (30), Diana (28)
InclusiveLeft[27, 32)Alice (30), Diana (28), Frank (27)
InclusiveRight(27, 32]Alice (30), Diana (28), Eve (32)

Output for InclusiveNeither

+-------+-------------+-----+--------+
| Name  | Department  | Age | Salary |
+-------+-------------+-----+--------+
| Alice | Engineering | 30  | 95000  |
| Diana | Sales       | 28  | 62000  |
+-------+-------------+-----+--------+
[2 rows x 4 columns]

String Bounds

Ranges work on text columns too, using lexicographic order:

result, err := df.Between("Name", "B", "D", dataframe.InclusiveBoth).Result()
if err != nil {
    log.Fatalf("Between failed: %v", err)
}
fmt.Println(result.String())

Output

+---------+-------------+-----+--------+
| Name    | Department  | Age | Salary |
+---------+-------------+-----+--------+
| Bob     | Sales       | 25  | 55000  |
| Charlie | Engineering | 35  | 105000 |
+---------+-------------+-----+--------+
[2 rows x 4 columns]

Note: A low greater than high is not an error; it simply matches nothing and returns an empty DataFrame.


Nlargest & Nsmallest

Keep the n highest or lowest rows of a column, ordered best first, similar to pandas df.nlargest(n, "Salary") and df.nsmallest(n, "Age").

Function Signatures

func (df *DataFrame) Nlargest(n int, column string) *FilterChain
func (df *DataFrame) Nsmallest(n int, column string) *FilterChain

Selection Rules

AspectBehaviour
Result orderRanked, not original row order: largest first for Nlargest, smallest first for Nsmallest
TiesThe row appearing earliest in the DataFrame wins (equivalent to pandas keep="first")
NullsNever ranked, so they are always excluded
n greater than row countReturns every non-null row instead of an error
n equal to 0Returns an empty DataFrame
Negative nReturns an error

Top N Example

The three highest salaries:

result, err := df.Nlargest(3, "Salary").Result()
if err != nil {
    log.Fatalf("Nlargest failed: %v", err)
}
fmt.Println(result.String())

Output

+---------+-------------+-----+--------+
| Name    | Department  | Age | Salary |
+---------+-------------+-----+--------+
| Charlie | Engineering | 35  | 105000 |
| Alice   | Engineering | 30  | 95000  |
| Frank   | Engineering | 27  | 88000  |
+---------+-------------+-----+--------+
[3 rows x 4 columns]

Bottom N Example

The two youngest employees:

result, err := df.Nsmallest(2, "Age").Result()
if err != nil {
    log.Fatalf("Nsmallest failed: %v", err)
}
fmt.Println(result.String())

Output

+-------+-------------+-----+--------+
| Name  | Department  | Age | Salary |
+-------+-------------+-----+--------+
| Bob   | Sales       | 25  | 55000  |
| Frank | Engineering | 27  | 88000  |
+-------+-------------+-----+--------+
[2 rows x 4 columns]

Requesting More Rows Than Exist

n larger than the number of rows returns everything available, fully ranked:

result, _ := df.Nlargest(10, "Age").Result()
fmt.Println(result.String())

Output

+---------+-------------+-----+--------+
| Name    | Department  | Age | Salary |
+---------+-------------+-----+--------+
| Charlie | Engineering | 35  | 105000 |
| Eve     | Marketing   | 32  | 72000  |
| Alice   | Engineering | 30  | 95000  |
| Diana   | Sales       | 28  | 62000  |
| Frank   | Engineering | 27  | 88000  |
| Bob     | Sales       | 25  | 55000  |
+---------+-------------+-----+--------+
[6 rows x 4 columns]

Ties

When values tie, earlier rows win. In this standings DataFrame, Red, Blue, and Green all have 10 points:

TeamPoints
Red10
Blue10
Green10
Gold4
result, _ := standings.Nlargest(2, "Points").Result()
fmt.Println(result.String())

Output

+------+--------+
| Team | Points |
+------+--------+
| Red  | 10     |
| Blue | 10     |
+------+--------+
[2 rows x 2 columns]

Supported Column Types

Column TypeRanking
float64 / int64 / intNumeric
stringLexicographic
bool and mixed-type columnsNot supported; returns an error

The ranking key is chosen from the column's first non-null value, so a column mixing numbers and text returns an error rather than an arbitrary order.

Performance

Nlargest and Nsmallest do not sort the column. They stream rows through a bounded heap that holds at most n entries, which keeps the cost proportional to the size of your result rather than the size of your data:

ApproachTimeExtra Space
Sort, then take nO(rows × log rows)O(rows)
Nlargest / NsmallestO(rows × log n)O(n)

For a small n over a large DataFrame, this is substantially cheaper than sorting. When you need the whole column ordered instead of a top slice, use SortValues.


Null Handling

Nulls are consistently excluded from every helper on this page, mirroring pandas where comparisons against NaN are False:

MethodNull Behaviour
Isin()Nulls never match, even when nil is included in values
Between()Nulls never fall inside a range
Nlargest()Nulls are never ranked
Nsmallest()Nulls are never ranked

Example

Using a DataFrame where Score contains two nulls:

NameScore
Ana88
Bennull
Cleo95
Devnull
Ela72
// Range: nulls are dropped even though the bounds are wide
inRange, _ := scores.Between("Score", 0.0, 100.0, dataframe.InclusiveBoth).Result()

// Top N: only the three non-null rows can be ranked
top, _ := scores.Nlargest(4, "Score").Result()

Output for Nlargest

+------+-------+
| Name | Score |
+------+-------+
| Cleo | 95    |
| Ana  | 88    |
| Ela  | 72    |
+------+-------+
[3 rows x 2 columns]

Note that Nlargest(4, ...) returned three rows: nulls are excluded rather than sorted to one end.

To select rows where a value is null, use Where:

missing, _ := scores.Where(func(row map[string]any) bool {
    return row["Score"] == nil
}).Result()

Chaining

Every helper returns a FilterChain, so they combine with each other and with Filter and Where to form an AND of all conditions. Order matters for Nlargest/Nsmallest: they rank whatever rows survive the preceding steps.

result, err := df.
    Isin("Department", []any{"Engineering", "Sales"}).
    Between("Age", 25, 30, dataframe.InclusiveBoth).
    Nlargest(2, "Salary").
    Result()
if err != nil {
    log.Fatalf("selection failed: %v", err)
}
fmt.Println(result.String())

Output

+-------+-------------+-----+--------+
| Name  | Department  | Age | Salary |
+-------+-------------+-----+--------+
| Alice | Engineering | 30  | 95000  |
| Frank | Engineering | 27  | 88000  |
+-------+-------------+-----+--------+
[2 rows x 4 columns]

Chain Evaluation Flow

Mixing with Filter and Where

result, err := df.
    Filter("Salary", dataframe.GreaterThan, 60000.0).
    Isin("Department", []any{"Engineering"}).
    Where(func(row map[string]any) bool {
        age, _ := row["Age"].(int64)
        return age < 35
    }).
    Result()

Output

+-------+-------------+-----+--------+
| Name  | Department  | Age | Salary |
+-------+-------------+-----+--------+
| Alice | Engineering | 30  | 95000  |
| Frank | Engineering | 27  | 88000  |
+-------+-------------+-----+--------+
[2 rows x 4 columns]

Terminating a Chain

MethodReturnsBehaviour
Result()(*DataFrame, error)Returns the result and the first error (if any)
Err()errorReturns the first error only
MustResult()*DataFrameReturns the result, panics if the chain holds an error

The first error is carried through the chain and later steps become no-ops:

_, err := df.
    Isin("Department", []any{"Engineering"}).
    Between("Missing", 1, 2, dataframe.InclusiveBoth). // error captured here
    Nlargest(2, "Salary").                            // skipped
    Result()
// err: "Between: column 'Missing' not found"

Index Preservation

Isin and Between keep matching rows in their original order with their original index labels. Nlargest and Nsmallest reorder rows by rank, and each row keeps its original label:

// Original index: 0, 1, 2, 3, 4, 5
membership, _ := df.Isin("Department", []any{"Sales"}).Result()
fmt.Println(membership.Index) // [1 3]

ranked, _ := df.Nlargest(3, "Salary").Result()
fmt.Println(ranked.Index) // [2 0 5]

Use ResetIndex() if you want the result renumbered from zero.


Error Handling

Common Errors

ErrorCauseSolution
"DataFrame is nil"Operating on a nil DataFrameCheck DataFrame initialization
"column 'X' not found"Invalid column nameVerify the column exists
"value of type X cannot be compared for membership"An Isin member is not a comparable type, such as a slicePass comparable values (numbers, strings, booleans)
"low and high must not be nil"A nil bound passed to BetweenProvide both bounds
"unsupported inclusive option 'X'"An Inclusive value outside the defined constantsUse InclusiveBoth, InclusiveNeither, InclusiveLeft, or InclusiveRight
"type mismatch: cannot compare"Between bounds are a different type from the columnCompare against values of the column's type
"n must not be negative"Negative n passed to Nlargest/NsmallestPass zero or a positive n
"cannot be ranked"Ranking a boolean or mixed-type columnRank a numeric or string column

Error Handling Example

result, err := df.
    Isin("Department", []any{"Engineering"}).
    Nlargest(3, "Salary").
    Result()
if err != nil {
    switch {
    case strings.Contains(err.Error(), "not found"):
        log.Fatal("Column doesn't exist in DataFrame")
    case strings.Contains(err.Error(), "cannot be ranked"):
        log.Fatal("Column type does not support ranking")
    default:
        log.Fatalf("Selection error: %v", err)
    }
}
fmt.Println(result.String())

Thread Safety

All four helpers are thread-safe:

MethodLock TypeDescription
Isin()RLockRead lock during row evaluation
Between()RLockRead lock during row evaluation
Nlargest()RLockRead lock during ranking
Nsmallest()RLockRead lock during ranking

Each step produces a new DataFrame, so the original is never mutated and concurrent selection is safe.


Complete Example

package main

import (
    "fmt"
    "log"

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

func main() {
    gp := gpandas.GoPandas{}

    df, err := gp.Read_csv("employees.csv")
    if err != nil {
        log.Fatalf("Failed to load data: %v", err)
    }

    // Target departments only
    target, err := df.Isin("Department", []any{"Engineering", "Marketing"}).Result()
    if err != nil {
        log.Fatalf("Isin failed: %v", err)
    }
    fmt.Println("Target departments:")
    fmt.Println(target.String())

    // Mid-career band within those departments
    midCareer, err := target.Between("Age", 27, 35, dataframe.InclusiveBoth).Result()
    if err != nil {
        log.Fatalf("Between failed: %v", err)
    }
    fmt.Println("Aged 27 to 35:")
    fmt.Println(midCareer.String())

    // Five highest earners in that band, in one chain
    topEarners, err := df.
        Isin("Department", []any{"Engineering", "Marketing"}).
        Between("Age", 27, 35, dataframe.InclusiveBoth).
        Nlargest(5, "Salary").
        Result()
    if err != nil {
        log.Fatalf("Selection failed: %v", err)
    }

    fmt.Println("Top 5 earners:")
    fmt.Println(topEarners.String())

    if _, err := topEarners.ToCSV("top_earners.csv", ","); err != nil {
        log.Printf("Export warning: %v", err)
    }
}

See Also

On this page