协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
ml-feature-engineering【开发与工程】Use this skill when asked about feature engineering, featuretools, tsfresh, feature selection, feature extraction, encoding, scaling, one-hot encoding, target encoding, feature interaction, datetime features, text features, or feature importance. This skill enforces: categorical encoding strategies (one-hot, label, target, ordinal), numerical scaling…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Transform raw data into effective ML features through encoding, scaling, extraction, interaction, and selection. Use automated feature engineering for relational and time-series data.
Exact user phrases: "feature engineering", "featuretools", "tsfresh", "feature selection", "feature extraction", "encoding", "scaling", "one-hot encoding", "target encoding", "feature interaction", "datetime features", "text features", "feature importance", "categorical encoding", "TF-IDF", "count vectorizer".
Before activating, verify:
Feature engineering pipeline with encoding, scaling, extraction, interaction, and selection as Python.
# Feature engineering pipeline
# Encoding and scaling transforms
# Feature selection implementation
# Feature definitions
# Automated FE configuration
# Feature importance ranking
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
400 lines of code and configuration.
Categorical feature cardinality
├── < 15 unique values → OneHotEncoder (handle_unknown="ignore")
│ For linear models: drop first category to avoid multicollinearity
│ For tree models: keep all categories
├── 15-100 unique values
│ ├── Ordered → OrdinalEncoder with explicit mapping
│ └── Unordered → BinaryEncoder or TargetEncoder with smoothing
├── 100-10000 unique values
│ ├── TargetEncoder (smoothing=10, min_samples_leaf=5)
│ ├── CatBoostEncoder (ordered target encoding, reduces leakage)
│ └── LeaveOneOutEncoder (if enough samples per category)
└── > 10000 unique values
├── CountEncoder (frequency as feature)
├── Hashing trick (n_components=2^16, signed=True)
└── Entity embeddings via neural network
Numerical feature distribution
├── Approximately normal → StandardScaler (zero mean, unit var)
├── Uniform distribution → MinMaxScaler (bounded [0,1])
├── Heavy outliers or skewed
│ ├── RobustScaler (median, IQR) — preserves outliers
│ └── Winsorize then StandardScaler — caps outliers
├── Highly skewed (> 1 skew)
│ ├── PowerTransformer (Yeo-Johnson) — handles negative
│ ├── QuantileTransformer (normal output) — ranks
│ └── Log-transform (for strictly positive)
└── Sparse data → StandardScaler(with_mean=False) or MaxAbsScaler
Data size and characteristic
├── Quick initial screening
│ ├── VarianceThreshold (remove constant/near-constant)
│ └── Correlation filter (remove >0.95 pairwise)
├── < 50K samples, < 500 features
│ ├── Mutual information (non-linear, captures any relationship)
│ ├── SelectKBest with f_classif (linear only)
│ └── RFE with LogisticRegression (wrapper, slow but accurate)
├── 50K-500K samples, 500-5000 features
│ ├── SelectFromModel with Lasso (L1 regularization)
│ ├── Permutation importance (model-agnostic)
│ └── LightGBM built-in importance (fast)
├── > 500K samples or > 5000 features
│ ├── L1-regularized linear model (fast feature elimination)
│ ├── RandomForest feature importance (parallel)
│ └── Boruta (shadow features, robust but slow)
└── High cardinality categorical
└── Target encoding → group rare categories → encode → select
One-hot encoding: nominal categories with < 50 unique values. Target encoding: high-cardinality categories, use smoothing to prevent overfitting. Ordinal encoding: ordered categories (education level, satisfaction). Binary encoding: high cardinality (hash categories to binary columns). Count encoding: frequency of each category. Leave-one-out encoding: target encoding without current row.
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, LabelEncoder
from category_encoders import TargetEncoder, BinaryEncoder, CatBoostEncoder
import pandas as pd
import numpy as np
def encode_categorical(df, cat_cols, target=None):
# One-hot for low cardinality
low_card = [c for c in cat_cols if df[c].nunique() < 15]
encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
onehot_encoded = encoder.fit_transform(df[low_card])
# Target encoding for high cardinality
high_card = [c for c in cat_cols if df[c].nunique() >= 15]
if high_card and target is not None:
te = TargetEncoder(cols=high_card, smoothing=10, min_samples_leaf=5)
target_encoded = te.fit_transform(df[high_card], target)
# Ordinal for ordered categories
ordinal_map = {"education_level": {
"High School": 0, "Bachelor": 1, "Master": 2, "PhD": 3
}}
for col, mapping in ordinal_map.items():
if col in df.columns:
df[f"{col}_ordinal"] = df[col].map(mapping)
return onehot_encoded
StandardScaler: zero mean, unit variance (default for most models). MinMaxScaler: bounded [0, 1] (neural networks, distance-based models). RobustScaler: median and IQR (outlier-robust). PowerTransformer: make data more Gaussian (Yeo-Johnson for positive and negative, Box-Cox for strictly positive). QuantileTransformer: uniform or normal distribution output. Fit on training data only, transform train and test.
from sklearn.preprocessing import StandardScaler, RobustScaler, MinMaxScaler, PowerTransformer
from scipy.stats import skew
def scale_numerical(df, num_cols):
scaled = pd.DataFrame(index=df.index)
for col in num_cols:
col_skew = skew(df[col].dropna())
if abs(col_skew) > 1:
pt = PowerTransformer(method="yeo-johnson")
scaled[f"{col}_power"] = pt.fit_transform(df[[col]])
elif df[col].std() > 10 * abs(df[col].median()):
rs = RobustScaler()
scaled[f"{col}_robust"] = rs.fit_transform(df[[col]])
else:
ss = StandardScaler()
scaled[f"{col}_standard"] = ss.fit_transform(df[[col]])
return scaled
Extract components: year, month, day, dayofweek, quarter, hour, minute, is_weekend, is_holiday, dayofyear, weekofyear. Cyclical encoding: sin/cos transform for cyclical features (month, dayofweek, hour). Difference features: days since last event, time between events. Lag features: previous values at t-1, t-7, t-30. Rolling window: rolling mean, std, min, max over window.
def extract_datetime_features(df, date_col):
dates = pd.to_datetime(df[date_col])
features = pd.DataFrame(index=df.index)
features["year"] = dates.dt.year
features["month"] = dates.dt.month
features["day"] = dates.dt.day
features["dayofweek"] = dates.dt.dayofweek
features["quarter"] = dates.dt.quarter
features["hour"] = dates.dt.hour
features["is_weekend"] = (dates.dt.dayofweek >= 5).astype(int)
features["month_sin"] = np.sin(2 * np.pi * features["month"] / 12)
features["month_cos"] = np.cos(2 * np.pi * features["month"] / 12)
features["dow_sin"] = np.sin(2 * np.pi * features["dayofweek"] / 7)
features["dow_cos"] = np.cos(2 * np.pi * features["dayofweek"] / 7)
reference = pd.Timestamp("2025-01-01")
features["days_since_ref"] = (dates - reference).dt.days
return features
def create_lag_features(df, group_col, value_col, lags=[1, 7, 30]):
features = pd.DataFrame(index=df.index)
for lag in lags:
features[f"{value_col}_lag_{lag}"] = (
df.groupby(group_col)[value_col].shift(lag)
)
for window in [7, 14, 30]:
rolling = df.groupby(group_col)[value_col].transform(
lambda x: x.rolling(window, min_periods=1).mean()
)
features[f"{value_col}_rolling_mean_{window}"] = rolling
return features
TF-IDF: term frequency-inverse document frequency, best for medium-length documents. CountVectorizer: simple word/phrase counts. N-grams: unigrams + bigrams typically sufficient. Sublinear TF: use sublinear_tf=True for dampened frequency. Vocabulary size: limit to 5000-50000 most frequent terms. Min/max document frequency: filter rare and ubiquitous terms. Word embeddings: pretrained Word2Vec/GloVe/FastText for dense representations.
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
def extract_text_features(texts, max_features=10000):
tfidf = TfidfVectorizer(
max_features=max_features,
stop_words="english",
ngram_range=(1, 2),
sublinear_tf=True,
min_df=5,
max_df=0.8,
)
tfidf_matrix = tfidf.fit_transform(texts)
text_df = pd.DataFrame(index=range(len(texts)))
text_df["char_count"] = texts.str.len()
text_df["word_count"] = texts.str.split().str.len()
text_df["avg_word_length"] = text_df["char_count"] / (text_df["word_count"] + 1)
text_df["capital_ratio"] = texts.str.findall(r"[A-Z]").str.len() / (text_df["char_count"] + 1)
text_df["digit_count"] = texts.str.findall(r"\d").str.len()
return tfidf_matrix, text_df
def text_to_avg_embeddings(texts, embedding_index, embed_dim=100):
embeddings = np.zeros((len(texts), embed_dim))
for i, text in enumerate(texts):
words = text.lower().split()
word_vectors = [embedding_index[w] for w in words if w in embedding_index]
if word_vectors:
embeddings[i] = np.mean(word_vectors, axis=0)
return embeddings
Polynomial features: degree 2 (x1*x2, x1^2, x2^2) — sufficient for most cases. Cross features: domain-specific interactions (product_category * season, user_tier * purchase_value). Ratio features: a/b where denominator > 0. Difference features: a - b for comparable columns. Aggregated features: groupby means, max, min, count per category.
from sklearn.preprocessing import PolynomialFeatures
def create_interactions(df, num_cols, cat_cols, target=None):
features = pd.DataFrame(index=df.index)
poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
poly_features = poly.fit_transform(df[num_cols])
poly_names = poly.get_feature_names_out(num_cols)
for name, vals in zip(poly_names, poly_features.T):
features[name] = vals
for cat in cat_cols[:10]:
if df[cat].nunique() < 20:
for num in num_cols[:5]:
group_means = df.groupby(cat)[num].transform("mean")
features[f"{cat}_{num}_ratio"] = df[num] / (group_means + 1e-10)
features[f"{cat}_{num}_diff"] = df[num] - group_means
for i in range(len(num_cols)):
for j in range(i+1, len(num_cols)):
col_a, col_b = num_cols[i], num_cols[j]
denominator = df[col_b].replace(0, np.nan)
features[f"{col_a}_div_{col_b}"] = df[col_a] / denominator
return features
Filter methods: correlation (Pearson for linear, Spearman for monotonic), mutual information (non-linear relationships), variance threshold (remove constant features), chi-square (categorical-categorical). Wrapper methods: recursive feature elimination (RFE), forward/backward selection. Embedded methods: L1 regularization (Lasso), tree-based importance (Random Forest, XGBoost). SelectKBest: keep top k features by score.
from sklearn.feature_selection import (
SelectKBest, mutual_info_classif, f_classif, RFE,
VarianceThreshold, SelectFromModel
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
def select_features(X, y, method="embedded", n_features=50):
if method == "filter":
selector = SelectKBest(mutual_info_classif, k=n_features)
X_selected = selector.fit_transform(X, y)
elif method == "wrapper":
estimator = LogisticRegression(max_iter=1000, penalty="l1", solver="saga")
selector = RFE(estimator, n_features_to_select=n_features, step=10)
X_selected = selector.fit_transform(X, y)
elif method == "embedded":
selector = SelectFromModel(
LogisticRegression(C=0.1, penalty="l1", solver="saga", max_iter=1000),
prefit=False, threshold="median",
)
X_selected = selector.fit_transform(X, y)
vt = VarianceThreshold(threshold=0.01)
X_selected = vt.fit_transform(X) if method == "none" else X_selected
return X_selected, selector
def get_feature_importance(X, y, feature_names):
model = RandomForestClassifier(n_estimators=200, max_depth=8, n_jobs=-1)
model.fit(X, y)
importance = pd.DataFrame({
"feature": feature_names,
"importance": model.feature_importances_,
}).sort_values("importance", ascending=False)
return importance
Featuretools: deep feature synthesis on relational data. Define entities and relationships, stack transform primitives (day, month, hour, time_since_previous) and aggregation primitives (count, sum, mean, std, max, min, trend, mode). Max depth: 2-3 for most use cases; deeper features overfit. tsfresh: automatic time-series feature extraction (hundreds of features per series). Apply after Featuretools transformation.
import featuretools as ft
from featuretools.primitives import (
Count, Sum, Mean, Std, Max, Min, Trend, Mode, Day, Month, Hour,
TimeSincePrevious, NumUnique, Entropy
)
def automated_feature_engineering(entities, relationships, target_entity, max_depth=2):
feature_matrix, feature_defs = ft.dfs(
entities=entities,
relationships=relationships,
target_entity=target_entity,
max_depth=max_depth,
agg_primitives=[Count, Sum, Mean, Std, Max, Min, Trend, Mode, NumUnique, Entropy],
trans_primitives=[Day, Month, Hour, TimeSincePrevious],
where_primitives=[Mean, Sum, Count],
max_features=200,
verbose=True,
)
return feature_matrix, feature_defs
For date columns without time component, extract: days since epoch, days until next event, relative to a reference date. Difference between multiple date columns yields duration features (e.g., order_to_shipping_days). For subscription/billing: days since last activity, days until renewal, account age.
def date_difference_features(df, date_col_a, date_col_b):
dates_a = pd.to_datetime(df[date_col_a])
dates_b = pd.to_datetime(df[date_col_b])
diff_days = (dates_b - dates_a).dt.days
return pd.DataFrame({f"{date_col_a}_to_{date_col_b}_days": diff_days})
Target encoding leaks target information if applied to full dataset before splitting. Always use within cross-validation:
from sklearn.model_selection import KFold
import numpy as np
def target_encode_cv(df, cat_col, target, n_folds=5, smoothing=10):
"""Target encoding with cross-validation to prevent leakage."""
encoded = np.zeros(len(df))
kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
for train_idx, val_idx in kf.split(df):
train_targets = df.iloc[train_idx][target]
global_mean = train_targets.mean()
for cat in df[cat_col].unique():
cat_mask = (df.iloc[train_idx][cat_col] == cat)
cat_count = cat_mask.sum()
cat_mean = train_targets[cat_mask].mean()
smoothed = (cat_mean * cat_count + global_mean * smoothing) / (cat_count + smoothing)
encoded[val_idx[df.iloc[val_idx][cat_col] == cat].index] = smoothed
return encoded
# Stratified version for classification
from sklearn.model_selection import StratifiedKFold
def target_encode_cv_classification(df, cat_col, target, n_folds=5, smoothing=10):
encoded = np.zeros(len(df))
skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
for train_idx, val_idx in skf.split(df, df[target]):
train_targets = df.iloc[train_idx][target]
global_mean = train_targets.mean()
for cat in df[cat_col].unique():
cat_mask = (df.iloc[train_idx][cat_col] == cat)
cat_count = cat_mask.sum()
cat_mean = train_targets[cat_mask].mean()
smoothed = (cat_mean * cat_count + global_mean * smoothing) / (cat_count + smoothing)
encoded[val_idx[df.iloc[val_idx][cat_col] == cat].index] = smoothed
return encoded
# Feast feature view with engineered features
from feast import FeatureView, Feature, Field
from feast.types import Float32, Int64, String
user_features = FeatureView(
name="user_engineered_features",
entities=["user_id"],
ttl=timedelta(days=1),
features=[
Field(name="user_total_purchases", dtype=Float32),
Field(name="user_days_since_last_purchase", dtype=Float32),
Field(name="user_avg_order_value", dtype=Float32),
Field(name="user_purchase_frequency_7d", dtype=Float32),
],
online=True,
source=user_features_source,
)
# feature_pipeline.yaml
feature_engineering:
encoding:
cat_low_cardinality_threshold: 15
cat_high_cardinality_strategy: target_encoding
smoothing: 10
min_samples_leaf: 5
scaling:
default: standard
outlier_strategy: robust
skewed_strategy: yeo_johnson
datetime:
extract_components: [year, month, day, dayofweek, quarter, hour]
cyclical_encode: [month, dayofweek, hour]
lags: [1, 7, 30]
interactions:
max_degree: 2
max_cat_num_interactions: 10
max_num_num_ratios: 5
validation:
check_leakage: true
check_future_info: true
max_correlation: 0.95
ml-classical-ml for model training with engineered features
ml-deep-learning for deep learning feature extraction (embeddings)
| Decision Point | Option A | Option B | Decision Criteria |
|---|---|---|---|
| Categorical cardinality | Low (< 10) → One-hot encode | High (> 50) → Target/embedding | Dimensionality constraints, tree vs linear model |
| Feature type | Numeric → Scale (Standard/MinMax) | Temporal → Cyclic encode (sin/cos) | Algorithm sensitivity, domain semantics |
| Missing values | Few (< 5%) → Impute (median) | Many (> 5%) → Flag + impute | Pattern in missingness, data volume |
| Feature interaction | Known interactions → Polynomial features | Unknown → Tree-based (handles automatically) | Model type, feature domain knowledge |
`python from sklearn.base import BaseEstimator, TransformerMixin from sklearn.preprocessing import FunctionTransformer import numpy as np import pandas as pd
def cyclical_encode(df, col, period): df[f'{col}_sin'] = np.sin(2 * np.pi * df[col] / period) df[f'{col}_cos'] = np.cos(2 * np.pi * df[col] / period) return df.drop(columns=[col])
class AggregationFeatures(BaseEstimator, TransformerMixin): def init(self, group_col, agg_col, aggs=['mean', 'std', 'count']): self.group_col = group_col self.agg_col = agg_col self.aggs = aggs
def fit(self, X, y=None):
self.agg_map_ = X.groupby(self.group_col)[self.agg_col].agg(self.aggs)
return self
def transform(self, X):
return X.join(self.agg_map_, on=self.group_col, rsuffix='_agg')
pipeline = Pipeline([ ('cyclical', FunctionTransformer( lambda df: cyclical_encode(df, 'hour', 24), validate=False )), ('aggregations', AggregationFeatures('user_id', 'amount')), ('scaler', StandardScaler()) ]) `
`python from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD import spacy
nlp = spacy.load('en_core_web_sm')
def extract_text_features(documents): tfidf = TfidfVectorizer( max_features=5000, ngram_range=(1, 2), stop_words='english', sublinear_tf=True ) tfidf_matrix = tfidf.fit_transform(documents)
svd = TruncatedSVD(n_components=100, random_state=42)
text_embeddings = svd.fit_transform(tfidf_matrix)
pos_features = np.array([
[token.pos_ for token in nlp(doc)].count('VERB')
/ max(len(doc), 1) for doc in documents
]).reshape(-1, 1)
return np.hstack([text_embeddings, pos_features])
`