Lookups#
This guide covers df-eval’s lookup functionality for integrating external data sources.
Overview#
Lookups allow you to resolve values from external data sources during expression evaluation. This is useful for:
Mapping codes to descriptions
Looking up prices or rates
Resolving configuration values
Fetching data from databases or APIs
Implementing business rules from external sources
Basic Lookup Usage#
The lookup function resolves values using a resolver:
import pandas as pd
from df_eval import lookup, DictResolver
mapping = {
"apple": 1.50,
"banana": 0.75,
"orange": 1.25,
}
resolver = DictResolver(mapping, default=0.0)
products = pd.Series(["apple", "banana", "cherry"])
prices = lookup(products, resolver, on_missing="null")
print(prices) # [1.50, 0.75, 0.0]
For a complete, runnable walkthrough that combines lookups with expression evaluation, see the example Engine Lookups with Resolvers.
Resolver Types#
DictResolver#
The simplest resolver for in-memory mappings:
from df_eval import DictResolver
mapping = {
"USD": 1.0,
"EUR": 0.85,
"GBP": 0.73,
"JPY": 110.0,
}
resolver = DictResolver(mapping, default=1.0)
currencies = pd.Series(["USD", "EUR", "GBP", "CAD"])
rates = lookup(currencies, resolver, on_missing="keep")
print(rates)
FileResolver#
Load mappings from CSV or JSON files:
from df_eval import FileResolver
resolver = FileResolver(
"prices.csv",
key_column="product",
value_column="price",
)
products = pd.Series(["apple", "banana", "cherry"])
prices = lookup(products, resolver, on_missing="null")
Custom Resolver Classes#
Create custom resolvers by inheriting from Resolver and implementing
resolve(self, key):
from df_eval import Resolver
class DatabaseResolver(Resolver):
def __init__(self, connection, table, key_col, value_col):
self.connection = connection
self.table = table
self.key_col = key_col
self.value_col = value_col
def resolve(self, key):
query = f"""
SELECT {self.value_col}
FROM {self.table}
WHERE {self.key_col} = ?
"""
result = pd.read_sql(query, self.connection, params=[key])
if len(result) == 0:
return None
return result[self.value_col].iloc[0]
Caching#
Improve performance by caching lookup results:
from df_eval import CachedResolver, FileResolver, lookup
base_resolver = FileResolver("prices.csv", "product", "price")
cached_resolver = CachedResolver(base_resolver, ttl_seconds=300)
prices1 = lookup(products, cached_resolver)
prices2 = lookup(products, cached_resolver)
Manual Cache Management#
cached_resolver.clear_cache()
Missing Value Handling#
Control how missing values are handled via on_missing:
# Return None for missing values
result = lookup(keys, resolver, on_missing="null")
# Raise error on missing values
try:
result = lookup(keys, resolver, on_missing="raise")
except ValueError as e:
print(f"Missing keys: {e}")
# Return the original key for missing values
result = lookup(keys, resolver, on_missing="keep")
Using Lookups in Pipelines#
Integrate lookups into df-eval pipelines by combining resolvers with regular
expressions. Lookups themselves are performed in Python using the helper
function df_eval.lookup.lookup() or via Pandera metadata.
Basic engine integration:
import pandas as pd
from df_eval import Engine, DictResolver, lookup
price_resolver = DictResolver({
"apple": 1.50,
"banana": 0.75,
"orange": 1.25,
})
engine = Engine()
engine.register_resolver("prices", price_resolver)
df = pd.DataFrame({
"product": ["apple", "banana", "orange"],
"quantity": [10, 20, 15],
})
prices = lookup(df["product"], price_resolver, on_missing="null")
df = df.assign(price=prices)
schema = {
"total": "price * quantity",
}
result = engine.apply_schema(df, schema)
See also the gallery example Engine Lookups with Resolvers for a slightly more complete pipeline using in-memory resolvers, and Lookup and Function Pipeline via Pandera for an end-to-end pipeline that stores lookup configuration in a Pandera schema.
Error Handling#
Handle lookup errors gracefully:
from df_eval import lookup
try:
result = lookup(keys, resolver, on_missing="raise")
except ValueError as e:
print(f"Lookup failed: {e}")
result = lookup(keys, resolver, on_missing="null")
Best Practices#
1. Use Caching for Expensive Lookups#
expensive_resolver = DatabaseResolver(...)
cached = CachedResolver(expensive_resolver, ttl_seconds=300)
2. Keep Resolver Logic Fast#
lookup calls resolve once per key in the input series. Prefer
resolvers that avoid repeated expensive network or database calls.
3. Handle Missing Values Explicitly#
result = lookup(keys, resolver, on_missing="keep")
Next Steps#
Check the API Reference for complete documentation
Review Basic Usage for core concepts
Explore Advanced Usage for more features