<jupyter_start><jupyter_text>Feathr Feature Store For Customer360 on Azure - Demo Notebook This notebook illustrates the use of Feathr Feature Store to create one of the use case for Customer 360. This usecase predicts Sales amount by the Discount offered. It includes following steps: 1. Install and set up Feathr with Azure2. Create shareable features with Feathr feature definition configs.3. Create a training dataset via point-in-time feature join.4. Compute and write features.5. Train a model using these features to predict Sales Amount.6. Materialize feature value to online store.7. Fetch feature value in real-time from online store for online scoring.The feature flow is as follows:![Feature Engineering](./Feature_engineering_c360.jpg)  Prerequisite: Provision cloud resourcesFirst step is to provision required cloud resources if you want to use Feathr. Feathr provides a python based client to interact with cloud resources.Please follow the steps [here](https://linkedin.github.io/feathr/how-to-guides/azure-deployment.html) to provision required cloud resources. Due to the complexity of the possible cloud environment, it is almost impossible to create a script that works for all the use cases. Because of this, [azure_resource_provision.sh](https://github.com/linkedin/feathr/blob/main/docs/how-to-guides/azure_resource_provision.sh) is a full end to end command line to create all the required resources, and you can tailor the script as needed, while [the companion documentation](https://github.com/linkedin/feathr/blob/main/docs/how-to-guides/azure-deployment.md) can be used as a complete guide for using that shell script.At the end of the script, it should give you some output which you will need later. For example, the Service Principal IDs, Redis endpoint, etc.Please also note that at the end of this step, you need to **manually** grant your service principal "Data Curator" permission of your Azure Purview account, due to a current limiation with Azure Purview.And the architecture is as below:![Architecture](https://github.com/linkedin/feathr/blob/main/docs/images/architecture.png?raw=true)  Sample DatasetIn this demo, we use Feathr Feature Store to showcase Customer360 Features using Feathr. The dataset can be mounted onto a azure blob storage account and seen by executing the following command. The dataset is present in the current directory and it is referenced from [here](https://community.tableau.com/s/question/0D54T00000CWeX8SAL/sample-superstore-sales-excelxls)<jupyter_code>key = "blobstorekey"
acnt = "studiofeathrazuredevsto"
container = "studio-feathrazure-dev-fs"
mntpnt = "/mnt/studio-feathrazure-dev-fs"

def mountStorageContainer(storageAccount, storageAccountKey, storageContainer, blobMountPoint):
    try:
        print("Mounting {0} to {1}:".format(storageContainer, blobMountPoint))
        dbutils.fs.unmount(blobMountPoint)
        
    except Exception as e:
        print("....Container is not mounted; Attempting mounting now..")
        
    mountStatus = dbutils.fs.mount(source = "wasbs://{0}@{1}.blob.core.windows.net/".format(storageContainer, storageAccount),
                  mount_point = blobMountPoint,
                  extra_configs = {"fs.azure.account.key.{0}.blob.core.windows.net".format(storageAccount): storageAccountKey})
    
    print("....Status of mount is: " + str(mountStatus))
    print()

    
mountStorageContainer(acnt,key,container,mntpnt)


df = spark.read.format("csv").option("header", "true").load("/mnt/studio-feathrazure-dev-fs/data/customer360.csv")
display(df)<jupyter_output><empty_output><jupyter_text>Prerequisite: Install FeathrInstall Feathr using pip:<jupyter_code>! pip install --force-reinstall git+https://github.com/linkedin/feathr.git@registry_fix#subdirectory=feathr_project pandavro scikit-learn<jupyter_output><empty_output><jupyter_text>Prerequisite: Configure the required environmentIn the first step (Provision cloud resources), you should have provisioned all the required cloud resources. If you use Feathr CLI to create a workspace, you should have a folder with a file called `feathr_config.yaml` in it with all the required configurations. Otherwise, update the configuration below.The code below will write this configuration string to a temporary location and load it to Feathr. Please still refer to [feathr_config.yaml](https://github.com/linkedin/feathr/blob/main/feathr_project/feathrcli/data/feathr_user_workspace/feathr_config.yaml) and use that as the source of truth. It should also have more explanations on the meaning of each variable.<jupyter_code>import tempfile
yaml_config = """

api_version: 1
project_config:
  project_name: 'customer360'
  required_environment_variables:
    - 'REDIS_PASSWORD'
    - 'AZURE_CLIENT_ID'
    - 'AZURE_TENANT_ID'
    - 'AZURE_CLIENT_SECRET'
    - 'ADLS_ACCOUNT'
    - 'ADLS_KEY'
    - 'WASB_ACCOUNT'
    - 'WASB_KEY'
    - 'DATABRICKS_WORKSPACE_TOKEN_VALUE '
    
offline_store:
  adls:
    adls_enabled: true
  wasb:
    wasb_enabled: true
  s3:
    s3_enabled: false
    s3_endpoint: 's3.amazonaws.com'
  jdbc:
    jdbc_enabled: false
    jdbc_database: 'feathrtestdb'
    jdbc_table: 'feathrtesttable'
  snowflake:
    snowflake_enabled: false
    url: "dqllago-ol19457.snowflakecomputing.com"
    user: "feathrintegration"
    role: "ACCOUNTADMIN"
spark_config:
  spark_cluster: 'databricks'
  spark_result_output_parts: '1'
  azure_synapse:
    dev_url: 'https://feathrazure.dev.azuresynapse.net'
    pool_name: 'spark3'
    workspace_dir: 'abfss://container@blobaccountname.dfs.core.windows.net/demo_data1/'
    executor_size: 'Small'
    executor_num: 4
    feathr_runtime_location: 'wasbs://public@azurefeathrstorage.blob.core.windows.net/feathr-assembly-LATEST.jar'
  databricks:
    workspace_instance_url: "https://adb-6578934.54.azuredatabricks.net/"
    workspace_token_value: ""
    config_template: '{"run_name":"","new_cluster":{"spark_version":"9.1.x-scala2.12","node_type_id":"Standard_D3_v2","num_workers":2,"spark_conf":{}},"libraries":[{"jar":""}],"spark_jar_task":{"main_class_name":"","parameters":[""]}}'
    
    work_dir: 'dbfs:/customer360'
    feathr_runtime_location: 'https://azurefeathrstorage.blob.core.windows.net/public/feathr-assembly-LATEST.jar'
online_store:
  redis:
    host: 'studio-feathrazure-dev-redis.redis.cache.windows.net'
    port: 6380
    ssl_enabled: True
feature_registry:
  purview:
    type_system_initialization: true
    purview_name: 'studio-feathrazure-dev-pview'
    delimiter: '__'
"""
# write this configuration string to a temporary location and load it to Feathr
tmp = tempfile.NamedTemporaryFile(mode='w', delete=False)
with open(tmp.name, "w") as text_file:
    text_file.write(yaml_config)<jupyter_output><empty_output><jupyter_text>Import necessary libraries<jupyter_code>import glob
import os
import tempfile
from datetime import datetime, timedelta
from math import sqrt
import pandas as pd
import pandavro as pdx
from feathr import FeathrClient
from feathr import BOOLEAN, FLOAT, INT32, ValueType,STRING
from feathr import Feature, DerivedFeature, FeatureAnchor
from feathr.anchor import FeatureAnchor
from feathr.client import FeathrClient
from feathr.feature_derivations import DerivedFeature
from feathr import BackfillTime, MaterializationSettings
from feathr import FeatureQuery, ObservationSettings
from feathr import RedisSink
from feathr import INPUT_CONTEXT, HdfsSource
from feathr import WindowAggTransformation
from feathr import TypedKey
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split<jupyter_output><empty_output><jupyter_text>Setup necessary environment variablesYou have to setup the environment variables in order to run this sample. More environment variables can be set by referring to [feathr_config.yaml](https://github.com/linkedin/feathr/blob/main/feathr_project/feathrcli/data/feathr_user_workspace/feathr_config.yaml) and use that as the source of truth. It should also have more explanations on the meaning of each variable.<jupyter_code>import os
os.environ['REDIS_PASSWORD'] = ''
os.environ['AZURE_CLIENT_ID'] = ''
os.environ['AZURE_TENANT_ID'] = ''
os.environ['AZURE_CLIENT_SECRET'] = ''
os.environ['ADLS_ACCOUNT'] = ''
os.environ['ADLS_KEY'] = ''
os.environ['WASB_ACCOUNT'] = ""
os.environ['WASB_KEY'] = ''
os.environ['DATABRICKS_WORKSPACE_TOKEN_VALUE'] = ''<jupyter_output><empty_output><jupyter_text>Initialize a feathr client<jupyter_code>client = FeathrClient(config_path=tmp.name)<jupyter_output><empty_output><jupyter_text>Define Sources SectionA feature source is needed for anchored features that describes the raw data in which the feature values are computed from. See the python documentation to get the details on each input column.<jupyter_code>batch_source = HdfsSource(name="cosmos_final_data",
                          path="abfss://container@blobaccountname.dfs.core.windows.net/data/customer360.csv",
                          event_timestamp_column="sales_order_dt",
                          timestamp_format="yyyy-MM-dd")<jupyter_output><empty_output><jupyter_text>Defining Features with Feathr:In Feathr, a feature is viewed as a function, mapping from entity id or key, and timestamp to a feature value.  Define Anchors and  FeaturesA feature is called an anchored feature when the feature is directly extracted from the source data, rather than computed on top of other features.<jupyter_code>f_sales_cust_id = Feature(name = "f_sales_cust_id",
                          feature_type = STRING, transform = "sales_cust_id" )

f_sales_tran_id = Feature(name = "f_sales_tran_id",
                          feature_type = STRING, transform = "sales_tran_id" )

f_sales_order_id = Feature(name = "f_sales_order_id",
                           feature_type = STRING, transform = "sales_order_id" )

f_sales_item_quantity = Feature(name = "f_sales_item_quantity", 
                                feature_type = INT32, transform = "cast_float(sales_item_quantity)" )

f_sales_order_dt = Feature(name = "f_sales_order_dt",
                           feature_type = STRING, transform = "sales_order_dt" )

f_sales_sell_price = Feature(name = "f_sales_sell_price",
                             feature_type = INT32, transform = "cast_float(sales_sell_price)" )

f_sales_discount_amt = Feature(name = "f_sales_discount_amt",
                               feature_type = INT32, transform = "cast_float(sales_discount_amt)" )

f_payment_preference = Feature(name = "f_payment_preference",
                               feature_type = STRING, transform = "payment_preference" )


features = [f_sales_cust_id, f_sales_tran_id, f_sales_order_id, f_sales_item_quantity, 
            f_sales_order_dt, f_sales_sell_price, f_sales_discount_amt, f_payment_preference]

request_anchor = FeatureAnchor(name="request_features",
                                source=INPUT_CONTEXT,
                                features=features)<jupyter_output><empty_output><jupyter_text>Define Derived FeaturesDerived features are the features that are computed from other features. They could be computed from anchored features, or other derived features.<jupyter_code>f_total_sales_amount = DerivedFeature(name = "f_total_sales_amount",
                                   feature_type = FLOAT,
                                   input_features = [f_sales_item_quantity,f_sales_sell_price],
                                   transform = "f_sales_item_quantity * f_sales_sell_price")

f_total_sales_discount= DerivedFeature(name = "f_total_sales_discount",
                                   feature_type = FLOAT,
                                   input_features = [f_sales_item_quantity,f_sales_discount_amt],
                                   transform = "f_sales_item_quantity * f_sales_discount_amt")


f_total_amount_paid= DerivedFeature(name = "f_total_amount_paid",
                                   feature_type = FLOAT,
                                   input_features = [f_sales_sell_price,f_sales_discount_amt],
                                   transform ="f_sales_sell_price - f_sales_discount_amt")<jupyter_output><empty_output><jupyter_text>Define Aggregate features and anchor the features to batch source.Note that if the data source is from the observation data, the source section should be INPUT_CONTEXT to indicate the source of those defined anchors.<jupyter_code>customer_ID = TypedKey(key_column="sales_cust_id",
                       key_column_type=ValueType.INT32,
                       description="customer ID",
                       full_name="cosmos.sales_cust_id")

agg_features = [Feature(name="f_avg_customer_sales_amount",
                        key=customer_ID,
                        feature_type=FLOAT,
                        transform=WindowAggTransformation(agg_expr="cast_float(sales_sell_price)",
                                                          agg_func="AVG",
                                                          window="1d")),
               
               Feature(name="f_avg_customer_discount_amount",
                        key=customer_ID,
                        feature_type=FLOAT,
                        transform=WindowAggTransformation(agg_expr="cast_float(sales_discount_amt)",
                                                          agg_func="AVG",
                                                          window="1d")),
               
              Feature(name="f_avg_item_ordered_by_customer",
                        key=customer_ID,
                        feature_type=FLOAT,
                        transform=WindowAggTransformation(agg_expr="cast_float(sales_item_quantity)",
                                                          agg_func="AVG",
                                                          window="1d"))]

agg_anchor = FeatureAnchor(name="aggregationFeatures",
                           source=batch_source,
                           features=agg_features)<jupyter_output><empty_output><jupyter_text>Building FeaturesAnd then we need to build those features so that it can be consumed later. Note that we have to build both the "anchor" and the "derived" features (which is not anchored to a source).<jupyter_code>client.build_features(anchor_list=[request_anchor,agg_anchor], derived_feature_list=[f_total_sales_amount, f_total_sales_discount,f_total_amount_paid])<jupyter_output><empty_output><jupyter_text>Registering FeaturesWe can also register the features with an Apache Atlas compatible service, such as Azure Purview, and share the registered features across teams:<jupyter_code>client.register_features()
client.list_registered_features(project_name="customer360")<jupyter_output><empty_output><jupyter_text>Create training data using point-in-time correct feature joinA training dataset usually contains entity id columns, multiple feature columns, event timestamp column and label/target column.To create a training dataset using Feathr, one needs to provide a feature join configuration file to specify what features and how these features should be joined to the observation data.<jupyter_code>feature_query = FeatureQuery(
    feature_list=["f_avg_item_ordered_by_customer","f_avg_customer_discount_amount","f_avg_customer_sales_amount","f_total_sales_discount"], key=customer_ID)
settings = ObservationSettings(
    observation_path="abfss://container@blobaccountname.dfs.core.windows.net/data/customer360.csv",
    event_timestamp_column="sales_order_dt",
    timestamp_format="yyyy-MM-dd")<jupyter_output><empty_output><jupyter_text>Materialize feature value into offline storageWhile Feathr can compute the feature value from the feature definition on-the-fly at request time, it can also pre-compute and materialize the feature value to offline and/or online storage.<jupyter_code>client.get_offline_features(observation_settings=settings,
                            feature_query=feature_query,
                            output_path="abfss://<EMAIL>@blobaccountname.dfs.core.windows.net/data/output/output.avro")
client.wait_job_to_finish(timeout_sec=500)<jupyter_output><empty_output><jupyter_text>Reading training data from offline storage<jupyter_code>path = '/mnt/studio-feathrazure-dev-fs/cosmos/output/output'
df= spark.read.format("avro").load(path)

df = df.toPandas()
display(df)<jupyter_output><empty_output><jupyter_text>Train a ML modelAfter getting all the features, let's train a machine learning model with the converted feature by Feathr:<jupyter_code>X = df['f_total_sales_discount']
y = df['f_total_sales_amount']

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 0.7, test_size = 0.3, random_state = 100)

# Add a constant to get an intercept
X_train_sm = sm.add_constant(X_train)

# Fit the resgression line using 'OLS'
lr = sm.OLS(y_train, X_train_sm).fit()

# Add a constant to X_test
X_test_sm = sm.add_constant(X_test)

# Predict the y values corresponding to X_test_sm
y_pred = lr.predict(X_test_sm)

# Checking the R-squared on the test set

r_squared = r2_score(y_test, y_pred)
r_squared


print("Model MAPE:")
print(1 - r_squared)
print()
print("Model Accuracy:")
print(r_squared)<jupyter_output><empty_output><jupyter_text>Materialize feature value into online storageWe can push the generated features to the online store like below<jupyter_code>redisSink = RedisSink(table_name="Customer360")
settings = MaterializationSettings("cosmos_feathr_table",
                                   sinks=[redisSink],
                                   feature_names=["f_avg_item_ordered_by_customer","f_avg_customer_discount_amount"])

client.materialize_features(settings)
client.wait_job_to_finish(timeout_sec=500)<jupyter_output><empty_output><jupyter_text>Fetching feature value for online inferenceFor features that are already materialized by the previous step, their latest value can be queried via the client's get_online_features or multi_get_online_features API.<jupyter_code>client.get_online_features(feature_table = "Customer360",
                           key = "<KEY>",
                           feature_names = ['f_avg_item_ordered_by_customer'])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08245893534032851, 'fraction_numerical': 0.00659231994726144, 'mean_word_length': 3.8145993123512296, 'pattern_counts': {'":': 11, '<': 75, '<?xml version=': 0, '>': 75, 'https://': 11, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 23, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '418777', 'license': 'Apache-2.0', 'n_tokens_mistral': 5533, 'n_tokens_neox': 5287, 'n_words': 1306, 'path': 'docs/samples/customer360/Customer360.ipynb', 'repo_name': 'Yuqing-cat/feathr', 'size': '18208'}