[ ]:
Data Wrangler Decorators Part 1: The @funnel Decorator
This tutorial introduces the powerful @funnel decorator, which automatically converts function inputs to pandas DataFrames. This allows you to write functions that work seamlessly with any data type that data-wrangler supports.
The @funnel Decorator
The @funnel decorator is the cornerstone of data-wrangler’s function integration system. It automatically wrangles function arguments into DataFrames, allowing your functions to work with:
Raw arrays, lists, and nested data structures
Text data (automatically embedded using NLP models)
Files and URLs
Mixed data types
Any other data type supported by data-wrangler
Let’s see how this works in practice.
[1]:
import datawrangler as dw
import pandas as pd
import numpy as np
from datawrangler import funnel
import matplotlib.pyplot as plt
Basic Example: Numerical Analysis Function
Let’s start with a simple function that computes basic statistics. Without @funnel, this would only work with DataFrames:
[2]:
# Define a function that works on DataFrames
@funnel
def compute_stats(data):
"""Compute basic statistics for numerical data"""
return {
'mean': data.mean().mean(),
'std': data.std().mean(),
'shape': data.shape,
'columns': list(data.columns)
}
# Test with different data types
print("=== Testing with different data types ===")
# 1. Raw numpy array
array_data = np.random.randn(10, 5)
print("\\n1. NumPy Array:")
print(f"Input shape: {array_data.shape}")
stats = compute_stats(array_data)
print(f"Result: {stats}")
# 2. 2D NumPy array (built from a nested Python list)
list_data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\\n2. 2D NumPy array:")
print(f"Input: {list_data}")
stats = compute_stats(list_data)
print(f"Result: {stats}")
# 3. Already a DataFrame
df_data = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
print("\\n3. Pandas DataFrame:")
print(f"Input shape: {df_data.shape}")
stats = compute_stats(df_data)
print(f"Result: {stats}")
=== Testing with different data types ===
\n1. NumPy Array:
Input shape: (10, 5)
Result: {'mean': np.float64(-0.16432272636920753), 'std': np.float64(1.0205481350367525), 'shape': (10, 5), 'columns': [0, 1, 2, 3, 4]}
\n2. 2D NumPy array:
Input: [[1 2 3]
[4 5 6]
[7 8 9]]
Result: {'mean': np.float64(5.0), 'std': np.float64(3.0), 'shape': (3, 3), 'columns': [0, 1, 2]}
\n3. Pandas DataFrame:
Input shape: (3, 2)
Result: {'mean': np.float64(3.5), 'std': np.float64(1.0), 'shape': (3, 2), 'columns': ['A', 'B']}
Text Processing with @funnel
One of the most powerful features is how @funnel handles text data automatically. Let’s create a function that analyzes text sentiment and see how it works with different text inputs:
[3]:
@funnel
def analyze_text_dimensions(text_data, text_kwargs={'model': 'all-MiniLM-L6-v2'}):
"""Analyze the dimensionality and characteristics of text embeddings"""
print(f"Received DataFrame with shape: {text_data.shape}")
print(f"Data type: {type(text_data)}")
print(f"Columns: {list(text_data.columns)}")
# Basic statistics about the embeddings
stats = {
'embedding_dimensions': text_data.shape[1],
'num_texts': text_data.shape[0],
'mean_embedding_magnitude': np.sqrt((text_data ** 2).sum(axis=1)).mean(),
'embedding_std': text_data.std().mean()
}
return stats
# Test with different text inputs
print("=== Testing text processing with @funnel ===")
# 1. Single text string
single_text = "This is a sample sentence for analysis."
print("\\n1. Single text string:")
print(f"Input: '{single_text}'")
result = analyze_text_dimensions(single_text)
print(f"Result: {result}")
# 2. List of texts
text_list = [
"Data science is fascinating.",
"Machine learning transforms industries.",
"Natural language processing enables AI communication.",
"Data wrangling simplifies preprocessing."
]
print("\\n2. List of texts:")
print(f"Input: {len(text_list)} texts")
result = analyze_text_dimensions(text_list)
print(f"Result: {result}")
=== Testing text processing with @funnel ===
\n1. Single text string:
Input: 'This is a sample sentence for analysis.'
/Users/jmanning/.pyenv/versions/3.10.12/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
loading corpus: minipedia...done!Received DataFrame with shape: (1, 50)
Data type: <class 'pandas.core.frame.DataFrame'>
Columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
Result: {'embedding_dimensions': 50, 'num_texts': 1, 'mean_embedding_magnitude': np.float64(0.5147815070493398), 'embedding_std': nan}
\n2. List of texts:
Input: 4 texts
Received DataFrame with shape: (4, 50)
Data type: <class 'pandas.core.frame.DataFrame'>
Columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
Result: {'embedding_dimensions': 50, 'num_texts': 4, 'mean_embedding_magnitude': np.float64(0.5669376195429721), 'embedding_std': np.float64(0.018058002593314436)}
The list_generalizer Decorator
@funnel converts inputs to DataFrames. A lower-level building block, @list_generalizer, does something complementary: it lets a function that operates on a single object automatically accept a list of objects, applying itself to each element and returning a list of results. data-wrangler uses it internally so that every wrangling function transparently supports lists of inputs.
[4]:
from datawrangler.decorate import list_generalizer
@list_generalizer
def describe(x):
"""Summarize a single array-like object."""
arr = np.asarray(x)
return {'shape': arr.shape, 'mean': float(arr.mean())}
# A single object -> a single result
print('Single object:')
print(describe(np.arange(10)))
# A list of objects -> a list of results (no manual loop required)
print()
print('List of objects:')
for result in describe([np.arange(10), np.ones((3, 3)), [5, 5, 5]]):
print(result)
Single object:
{'shape': (10,), 'mean': 4.5}
List of objects:
{'shape': (10,), 'mean': 4.5}
{'shape': (3, 3), 'mean': 1.0}
{'shape': (3,), 'mean': 5.0}