In many cases, it's important to write features about a composite of two or more objects. For instance, in content recommendation, there are some features which may only be defined for (user, content) pair e.g. affinity of user with content creator. Even these complex features can be naturally modeled using Fennel featuresets. Here is a relatively complex example:
1@featureset 2class User: 3 id: int = feature(id=1) 4 name: str = feature(id=2) 5 .. 6 7@featureset 8class Post: 9 id: int = feature(id=1) 10 creator_uid: int = feature(id=2) 11 ... 12 13 @extractor 14 def creator(cls, ts: pd.Series[datetime], pids: pd.Series[id]) -> Series[creator_uid]: 15 <some code here> 16 ... 17 18@featureset 19class UserCreator: 20 # describes features for (uid, creator_uid) pairs 21 viewer: int = feature(id=1) 22 creator: int = feature(id=2) 23 affinity: float = feature(id=3) 24 ... 25 26 @extractor 27 def affinity_fn(cls, ts: pd.Series[datetime], viewers: pd.Series[viewer], 28 creators: pd.Series[creator]) -> Series[affinity]: 29 <some code here> 30 ... 31 32@featureset 33class UserPost: 34 # describes features for (uid, pid) pairs 35 uid: int = feature(id=1) 36 pid: int = feature(id=2) 37 viewer_author_affinity = feature(id=3) 38 ... 39 40 @extractor 41 def fn(cls, ts: pd.Series[datetime], uids: pd.Series[uid], pids: pd.Series[pid]): 42 creators = Post.creator(ts, pids) 43 return UserCreator.affinity_fn(ts, uids, creators) 44 45
A lot is happening here. In addition to featureset for
Post, this example also has a couple of composite featuresets --
UserCreator to capture features for (uid, creator) pairs and
UserPost for capturing features of (uid, post) pairs.
Further, extractors can depend on extractors of other featuresets - here is line 42, the extractor first uses an extractor of
Post featureset to get creators for the posts and then users an extractor of
UserCreator to get the affinity between those users and creators.
This way, it's possible to build very complex features by reusing machinery built for other existing features. And Fennel is smart enough to figure out the best way to resolve dependencies across featuresets (or throw an error if they can't be satisfied).