Composite Features

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:

2class User:
3    id: int = feature(id=1)
4    name: str = feature(id=2)
5    ..
8class Post:
9    id: int = feature(id=1)
10    creator_uid: int = feature(id=2)    
11    ...
13    @extractor
14    def creator(cls, ts: pd.Series[datetime], pids: pd.Series[id]) -> Series[creator_uid]:
15        <some code here>
16        ...
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    ...
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        ...
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    ...
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)

A lot is happening here. In addition to featureset for User, and 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).