<jupyter_start><jupyter_text>Env Setting<jupyter_code>import sys 
# sys.path.clear()
sys.path.insert(0, 'D:\\Anaconda\\envs\\tensorflow_cpu\\python36.zip')
sys.path.insert(0, 'D:\\Anaconda\\envs\\tensorflow_cpu\\DLLs')
sys.path.insert(0, 'D:\\Anaconda\\envs\\tensorflow_cpu\\lib')
sys.path.insert(0, 'D:\\Anaconda\\envs\\tensorflow_cpu')
sys.path.insert(0, 'D:\\Anaconda\\envs\\tensorflow_cpu\\lib\\site-packages')
sys.path.insert(0, '')
from __future__ import absolute_import, division, print_function, unicode_literals

# TensorFlow and tf.keras
import tensorflow as tf
from tensorflow import keras

print(tf.__version__)
from scipy.sparse import csr_matrix, load_npz, save_npz
from tqdm import tqdm
from sklearn.preprocessing import normalize
import datetime
import json
import pandas as pd
import time
import yaml
import scipy.sparse as sparse
from ast import literal_eval

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import normalize
from sklearn.linear_model import Ridge
from sklearn.utils.extmath import randomized_svd

from scipy.optimize import minimize<jupyter_output><empty_output><jupyter_text>Loading data<jupyter_code># Load Original Data
df_train = pd.read_csv('../../data/yelp/Train.csv',encoding='latin-1')
df_valid = pd.read_csv('../../data/yelp/Valid.csv',encoding='latin-1')
df_test = pd.read_csv('../../data/yelp/Test.csv',encoding='latin-1')
keyphrases = pd.read_csv('../../data/yelp/KeyPhrases.csv')['Phrases'].tolist()
keyphrase_popularity = np.loadtxt('../data/yelp/'+'keyphrase_popularity.txt', dtype=int)
# Load U-I Data 
rtrain = load_npz("../../data/yelp/Rtrain.npz")
rvalid = load_npz("../../data/yelp/Rvalid.npz")
rtest = load_npz("../../data/yelp/Rtest.npz")
# Load user/item keyphrase data
U_K = load_npz("../../data/yelp/U_K.npz")
I_K = load_npz("../../data/yelp/I_K.npz")<jupyter_output><empty_output><jupyter_text>Forward Models<jupyter_code># Models
from sklearn.metrics.pairwise import cosine_similarity
def train(matrix_train):
    similarity = cosine_similarity(X=matrix_train, Y=None, dense_output=True)
    return similarity

def get_I_K(df, row_name = 'ItemIndex', shape = (3668,75)):
    rows = []
    cols = []
    vals = []
    for i in tqdm(range(df.shape[0])):
        key_vector = literal_eval(df['keyVector'][i])
        rows.extend([df[row_name][i]]*len(key_vector)) ## Item index
        cols.extend(key_vector) ## Keyword Index
        vals.extend(np.array([1]*len(key_vector)))
    return csr_matrix((vals, (rows, cols)), shape=shape)



def prediction(prediction_score, topK, matrix_Train):

    prediction = []

    for user_index in tqdm(range(matrix_Train.shape[0])):
        vector_u = prediction_score[user_index]
        vector_train = matrix_Train[user_index]
        if len(vector_train.nonzero()[0]) > 0:
            vector_predict = sub_routine(vector_u, vector_train, topK=topK)
        else:
            vector_predict = np.zeros(topK, dtype=np.float32)

        prediction.append(vector_predict)

    return np.vstack(prediction)


def sub_routine(vector_u, vector_train, topK=500):

    train_index = vector_train.nonzero()[1]

    vector_u = vector_u

    candidate_index = np.argpartition(-vector_u, topK+len(train_index))[:topK+len(train_index)]
    vector_u = candidate_index[vector_u[candidate_index].argsort()[::-1]]
    vector_u = np.delete(vector_u, np.isin(vector_u, train_index).nonzero()[0])

    return vector_u[:topK]

def predict(matrix_train, k, similarity, item_similarity_en = False):
    """
    res = similarity * matrix_train    if item_similarity_en = False
    res = similarity * matrix_train.T  if item_similarity_en = True
    """
    prediction_scores = []
    
    if item_similarity_en:
        matrix_train = matrix_train.transpose()
        
    for user_index in tqdm(range(matrix_train.shape[0])):
        # Get user u's prediction scores to all users
        vector_u = similarity[user_index]

        # Get closest K neighbors excluding user u self
        similar_users = vector_u.argsort()[::-1][1:k+1]
        # Get neighbors similarity weights and ratings
        similar_users_weights = similarity[user_index][similar_users]
        similar_users_ratings = matrix_train[similar_users].toarray()

        prediction_scores_u = similar_users_ratings * similar_users_weights[:, np.newaxis]

        prediction_scores.append(np.sum(prediction_scores_u, axis=0))
    res = np.array(prediction_scores)
    
    if item_similarity_en:
        res = res.transpose()
    
    return res

def predict_vector(user_index, matrix_train, k, similarity):
    """
    res = similarity * matrix_train    if item_similarity_en = False
    res = similarity * matrix_train.T  if item_similarity_en = True
    get only user_index row
    """
    vector_u = similarity[user_index]
    
    # Get closest K neighbors excluding user u self
    similar_users = vector_u.argsort()[::-1][1:k+1]
    # Get neighbors similarity weights and ratings
    similar_users_weights = similarity[user_index][similar_users]
    similar_users_ratings = matrix_train[similar_users].toarray()
    prediction_scores_u = similar_users_ratings * similar_users_weights[:, np.newaxis]
    
    return np.sum(prediction_scores_u, axis=0)<jupyter_output><empty_output><jupyter_text>Eval Models<jupyter_code># Evluation 
def recallk(vector_true_dense, hits, **unused):
    hits = len(hits.nonzero()[0])
    return float(hits)/len(vector_true_dense)

def precisionk(vector_predict, hits, **unused):
    hits = len(hits.nonzero()[0])
    return float(hits)/len(vector_predict)


def average_precisionk(vector_predict, hits, **unused):
    precisions = np.cumsum(hits, dtype=np.float32)/range(1, len(vector_predict)+1)
    return np.mean(precisions)


def r_precision(vector_true_dense, vector_predict, **unused):
    vector_predict_short = vector_predict[:len(vector_true_dense)]
    hits = len(np.isin(vector_predict_short, vector_true_dense).nonzero()[0])
    return float(hits)/len(vector_true_dense)


def _dcg_support(size):
    arr = np.arange(1, size+1)+1
    return 1./np.log2(arr)


def ndcg(vector_true_dense, vector_predict, hits):
    idcg = np.sum(_dcg_support(len(vector_true_dense)))
    dcg_base = _dcg_support(len(vector_predict))
    dcg_base[np.logical_not(hits)] = 0
    dcg = np.sum(dcg_base)
    return dcg/idcg


def click(hits, **unused):
    first_hit = next((i for i, x in enumerate(hits) if x), None)
    if first_hit is None:
        return 5
    else:
        return first_hit/10


def evaluate(matrix_Predict, matrix_Test, metric_names =['R-Precision', 'NDCG', 'Precision', 'Recall', 'MAP'], atK = [5, 10, 15, 20, 50], analytical=False):
    """
    :param matrix_U: Latent representations of users, for LRecs it is RQ, for ALSs it is U
    :param matrix_V: Latent representations of items, for LRecs it is Q, for ALSs it is V
    :param matrix_Train: Rating matrix for training, features.
    :param matrix_Test: Rating matrix for evaluation, true labels.
    :param k: Top K retrieval
    :param metric_names: Evaluation metrics
    :return:
    """
    global_metrics = {
        "R-Precision": r_precision,
        "NDCG": ndcg,
        "Clicks": click
    }

    local_metrics = {
        "Precision": precisionk,
        "Recall": recallk,
        "MAP": average_precisionk
    }

    output = dict()

    num_users = matrix_Predict.shape[0]

    for k in atK:

        local_metric_names = list(set(metric_names).intersection(local_metrics.keys()))
        results = {name: [] for name in local_metric_names}
        topK_Predict = matrix_Predict[:, :k]

        for user_index in tqdm(range(topK_Predict.shape[0])):
            vector_predict = topK_Predict[user_index]
            if len(vector_predict.nonzero()[0]) > 0:
                vector_true = matrix_Test[user_index]
                vector_true_dense = vector_true.nonzero()[1]
                hits = np.isin(vector_predict, vector_true_dense)

                if vector_true_dense.size > 0:
                    for name in local_metric_names:
                        results[name].append(local_metrics[name](vector_true_dense=vector_true_dense,
                                                                 vector_predict=vector_predict,
                                                                 hits=hits))

        results_summary = dict()
        if analytical:
            for name in local_metric_names:
                results_summary['{0}@{1}'.format(name, k)] = results[name]
        else:
            for name in local_metric_names:
                results_summary['{0}@{1}'.format(name, k)] = (np.average(results[name]),
                                                              1.96*np.std(results[name])/np.sqrt(num_users))
        output.update(results_summary)

    global_metric_names = list(set(metric_names).intersection(global_metrics.keys()))
    results = {name: [] for name in global_metric_names}

    topK_Predict = matrix_Predict[:]

    for user_index in tqdm(range(topK_Predict.shape[0])):
        vector_predict = topK_Predict[user_index]

        if len(vector_predict.nonzero()[0]) > 0:
            vector_true = matrix_Test[user_index]
            vector_true_dense = vector_true.nonzero()[1]
            hits = np.isin(vector_predict, vector_true_dense)

            # if user_index == 1:
            #     import ipdb;
            #     ipdb.set_trace()

            if vector_true_dense.size > 0:
                for name in global_metric_names:
                    results[name].append(global_metrics[name](vector_true_dense=vector_true_dense,
                                                              vector_predict=vector_predict,
                                                              hits=hits))

    results_summary = dict()
    if analytical:
        for name in global_metric_names:
            results_summary[name] = results[name]
    else:
        for name in global_metric_names:
            results_summary[name] = (np.average(results[name]), 1.96*np.std(results[name])/np.sqrt(num_users))
    output.update(results_summary)

    return output<jupyter_output><empty_output><jupyter_text>Forward Initial Prediction  User-Item KNN<jupyter_code>similarity = normalize(train(rtrain))
user_item_prediction_score = predict(rtrain, 100, similarity, item_similarity_en= False)
user_item_predict = prediction(user_item_prediction_score, 50, rtrain)
user_item_res = evaluate(user_item_predict, rtest)
# k = 100 
user_item_res<jupyter_output><empty_output><jupyter_text>Latent Similarity Matrix Learned with Linear Regression¶<jupyter_code># Training 
X = normalize(U_K.todense())
y = normalize(train(rtrain))
clf = Ridge(alpha=0.1).fit(X, y) # Optimality at L2 regularization = 0.1
lr_similarity = clf.predict(np.array(X))
# Prediciting
similarity = lr_similarity
lr_prediction_score = predict(rtrain, 100, similarity, item_similarity_en= False)
lr_predict = prediction(lr_prediction_score, 50, rtrain)
lr_res = evaluate(lr_predict, rtest)
# k = 100
lr_res<jupyter_output><empty_output><jupyter_text>Critiquing<jupyter_code># One hot encoding of critiquing
def get_critiqued_UK(user_keyphrase_frequency,user_index,critiqued_keyphrase):
    """
    user_keyphrase_frequency is the U_K matrix (csr sparse matrix)
    return the one-hot encoding of the critique
    """
    U_K_cp = user_keyphrase_frequency.copy()
    U_K_cp[user_index] = 0
    U_K_cp[user_index,critiqued_keyphrase] = 1
    return U_K_cp

def project_one_hot_encoding(reg, user_keyphrase_frequency,user_index = 0,critiqued_keyphrase = 0, normalize_en = True):
    """
    Return the projection on user_sim space from one-hot encoding of critiqued keyphrase
    The res[user_index] should be target embedding row
    """
    critiqued_matrix = get_critiqued_UK(user_keyphrase_frequency, user_index, critiqued_keyphrase)
    res = reg.predict(critiqued_matrix)
    if normalize_en:
        res = normalize((res))
    return res
def get_initial_predictions(X = normalize(U_K.todense()), y = normalize(train(rtrain)),
                            matrix_Train = rtrain, k = 100):
    clf = Ridge(alpha=0.1).fit(X, y)
    similarity = normalize(train(matrix_Train))
    user_item_prediction_score = predict(matrix_Train, k, similarity, item_similarity_en= False)
    return user_item_prediction_score, clf
def get_valid_keyphrases(keyphrase_freq,top_recommendations,item = None,threshold=50,mutiple_keyphrases_en = False, top_items = None):
    """
    Wrapper function to get either top 1 or top n keyphrases
    """
    if mutiple_keyphrases_en:
        top_keyphrases = []
        for item in top_items:
            top_keyphrases.extend(get_valid_keyphrases_for_one_item(keyphrase_freq,top_recommendations,item,threshold=threshold))
        return np.ravel(list(set(top_keyphrases))) # remove duplicate and reformat to np array
    else:
        return get_valid_keyphrases_for_one_item(keyphrase_freq,top_recommendations,item,threshold=threshold)

def get_valid_keyphrases_for_one_item(keyphrase_freq,top_recommendations, item,threshold=50):
    """
    Get keyphrases of item that make sense
    E.g. if the item has fewer than threshold=50 keyphrases, get all of them
    otherwise get top 50 keyphrases
    """
    keyphrase_length = len(keyphrase_freq[item].nonzero()[1])
    if keyphrase_length<threshold:
        return keyphrase_freq[item].nonzero()[1]
    else:
        keyphrases = np.ravel(keyphrase_freq[top_recommendations[0]].todense())
        top_keyphrases = np.argsort(keyphrases)[::-1][:threshold]
        return top_keyphrases
    
def predict_vector(user_index, matrix_train, k, similarity, with_keyphrase = False, 
                   keyphrase_freq = None, critiqued_keyphrase = None, alpha = 0):
    """
    get only user_index row
    if with_keyphrase = True, then penalize items without critiqued_keyphrase to alpha (default = 0)
    """
    vector_u = similarity[user_index]
    
    # Get closest K neighbors excluding user u self
    similar_users = vector_u.argsort()[::-1][1:k+1]
    # Get neighbors similarity weights and ratings
    similar_users_weights = similarity[user_index][similar_users]
    similar_users_ratings = matrix_train[similar_users].toarray()
    
    prediction_scores_u = similar_users_ratings * similar_users_weights[:, np.newaxis]
    
    if with_keyphrase == False:
        return np.sum(prediction_scores_u, axis=0)
    
    # Only Predict items with critiqued_keyphrase 
    else:
        prediction_scores = np.sum(prediction_scores_u, axis=0)
#         print (prediction_scores)
        #penalize items without critiqued keyphrase
        items_with_keyphrase = np.ravel(keyphrase_freq.T[critiqued_keyphrase].nonzero()[1])
#         print (items_with_keyphrase)
        #Return the unique values in ar1 that are not in ar2.
        items_without_keyphrase = np.setdiff1d(np.arange(matrix_train.shape[1]), items_with_keyphrase)
        prediction_scores[items_without_keyphrase] = alpha # penalize
        return prediction_scores
#         print (prediction_scores)
#         return prediction_scores/sum(prediction_scores)

    
def get_initial_prediction(user,X = normalize(U_K.todense()), y = normalize(train(rtrain)),
                            matrix_Train = rtrain, k = 100):
    """
    Get the initial knn predictions before critiquing pipelines
    get the linear regression model for critiquing embedding (W_2)
    get the initial user similarity matrix 
    k here is the parameter for KNN
    """
    clf = Ridge(alpha=0.1).fit(X, y)
    similarity = normalize(train(matrix_Train))
    user_item_prediction_score = predict_vector(user, matrix_Train, k, similarity)
    return user_item_prediction_score, clf


# For keyphrase selecting method # 3 "diff" 
def get_item_keyphrase_freq(keyphrase_freq,item):
    """
    Get item's keyphrase frequency 
    """
    count = keyphrase_freq[item].todense()
    return count/np.sum(count)
# Utility function for getting restaurant info from ItemIndex
def get_business_df(path = "../../data/yelp/business.json" ):
    with open(path,encoding="utf8") as json_file:
        data = json_file.readlines()
        data = list(map(json.loads, data))
    df = pd.DataFrame(data)
    
    return df

def get_restaurant_info(business_df, business_id, name = True, review_count = True, stars = True ):
    output_list = {}
    row_idx = int(business_df.index[business_df['business_id'] == business_id].tolist()[0])
    if name == True:
        output_list['name'] = business_df['name'][row_idx].encode('utf-8').strip()
    if review_count == True:
        output_list['review_count'] = business_df['review_count'][row_idx]
    if stars == True:
        output_list['stars'] = business_df['stars'][row_idx] 
    return output_list

# def get_businessid_from_Itemindex(ItemIndex_list, itemindex):
#     return ItemIndex_list['business_id'].tolist()[itemindex]

def get_restaurant_name(df_train, business_df, ItemIndex):
    rows = np.where(df_train['ItemIndex'] == ItemIndex)
    if len(rows)!= 0:
        business_id = df_train.loc[rows[0][0]]['business_id']
        item_info = get_restaurant_info(business_df, business_id)
        return item_info['name']
    return "NOT_FOUND"
def get_keyphrase_popularity(df,keyphrases):
    """
    Get keyphrase popularity (count) from dataframe
    """
    keyphrase_popularity = np.zeros(len(keyphrases)) #initialize
    for i in range(len(df)):
        keyphrase_vector = literal_eval(df['keyVector'][i])
        keyphrase_popularity[keyphrase_vector] += 1 # count
    return keyphrase_popularity<jupyter_output><empty_output><jupyter_text>PLREC<jupyter_code>def inhour(elapsed):
    return time.strftime('%H:%M:%S', time.gmtime(elapsed))

def plrec(matrix_train, iteration=4, lamb=80, rank=200, seed=1):
    """
    Function used to achieve generalized projected lrec w/o item-attribute embedding
    :param matrix_train: user-item matrix with shape m*n
    :param iteration: number of power iterations in randomized svd
    :param lamb: parameter of penalty
    :param rank: latent dimension size
    :param seed: the seed of the pseudo random number generator to use when shuffling the data
    :return: prediction in sparse matrix
    """
    print ("Randomized SVD")
    start_time = time.time()
    P, sigma, Qt = randomized_svd(matrix_train,
                                  n_components=rank,
                                  n_iter=iteration,
                                  random_state=seed)

    RQ = matrix_train.dot(sparse.csc_matrix(Qt.T*np.sqrt(sigma)))

    print("Elapsed: {}".format(inhour(time.time() - start_time)))

    print ("Closed-Form Linear Optimization")
    start_time = time.time()
    pre_inv = RQ.T.dot(RQ) + lamb * sparse.identity(rank, dtype=np.float32)
    inverse = sparse.linalg.inv(pre_inv.tocsc())
    Y = inverse.dot(RQ.T).dot(matrix_train)
    print("Elapsed: {}".format(inhour(time.time() - start_time)))

    return np.array(RQ.todense()), np.array(Y.todense()), None

def predict_vector(rating_vector, train_vector, remove_train=True):
    dim = len(rating_vector)
    candidate_index = np.argpartition(-rating_vector, dim-1)[:dim]
    prediction_items = candidate_index[rating_vector[candidate_index].argsort()[::-1]]
    
    if remove_train:
        return np.delete(prediction_items, np.isin(prediction_items, train_vector.nonzero()[1]).nonzero()[0])
    else:
        return prediction_items

    
    
def predict_scores(matrix_U, matrix_V, bias=None,
                   penalize = False,
                   keyphrase_freq = I_K, 
                   critiqued_keyphrase = 0, 
                   matrix_Train = rtrain,
                   alpha = 0):
    prediction = matrix_U.dot(matrix_V.T)
    # Penalize
    if penalize == True:
        items_with_keyphrase = np.ravel(keyphrase_freq.T[critiqued_keyphrase].nonzero()[1])
        items_without_keyphrase = np.setdiff1d(np.arange(matrix_Train.shape[1]), items_with_keyphrase)
        prediction[items_without_keyphrase] = alpha # penalize
    
    return prediction

predict_scores(matrix_U=RQ,
                                      matrix_V=Y).T[0]
Y.shape
I_K[:1].nonzero()[1]
list(set([1,1,2,3,4]))
def get_all_affected_items(wanted_keyphrases,keyphrase_freq):
    res = []
    for keyphrase in wanted_keyphrases:
        items = np.ravel(keyphrase_freq.T[keyphrase].nonzero()[1])
        res.extend(items)
    return np.array(list(set(res)))
    
def select_only_wanted_keyphrase(top_recommendations, wanted_keyphrases, keyphrase_freq, matrix_Train = rtrain):
    all_items_with_keyphrases = get_all_affected_items(wanted_keyphrases,keyphrase_freq)
    affected_items = np.setdiff1d(np.arange(matrix_Train.shape[1]), all_items_with_keyphrases) # Get all other keyphrases
    top_recommendations[~np.in1d(top_recommendations, affected_items)]
    return top_recommendations

def pruning(prediction_score, 
           wanted_keyphrases_random, 
           top_recommendations, 
           keyphrase_freq, 
           matrix_Train = rtrain,
           alpha = 0):
    items_with_keyphrase = get_all_affected_items(wanted_keyphrases_random, keyphrase_freq)
    #Return the unique values in ar1 that are not in ar2.
    items_without_keyphrase = np.setdiff1d(np.arange(matrix_Train.shape[1]), items_with_keyphrase)
#     print (items_without_keyphrase)
    print (sum(prediction_score[items_without_keyphrase]))
    score = np.copy(prediction_score)
    score[items_without_keyphrase] = alpha # penalize
    return score
def single_step_critiquing_plrec(user = 2, 
                           keyphrase_length_threshold = 150, 
                           max_iteration_threshold = 5,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity, 
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all',
                           lams = [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],
                           reg = reg, Y = Y, RQt = RQt, Bias = Bias
                          ):
    """
    k: HR@k 
    keyphrase_length_threshold: limit the number of keyphrases in top recommended item
    keyphrase_selection_method: 'random': randomly select keyphrase from wanted_keyphrases
                                'pop': always select the most popular keyphrase in wanted_keyphrases
                                'diff': select the keyphrase with largest frequency difference between top recommended 
                                        item and target item.
    recommend_type: 'all': recommend all items
                    'upper' (only_with_critiqued_keyphrase): recommend items with only critiqued_keyphrase
    lam: modified_matrix = lam*origianl_matrix + (1-lam)*critiquing_embedding 
    """
    
    row['user_id'] = user
    print ('User ID ', user)
    
    # Set up (move to header line later)
    matrix_Train = rtrain
    matrix_Test = rtest
    keyphrase_freq = I_K
    num_items = rtrain.shape[1]
    max_wanted_keyphrase = 10 # for keyphrase_selection_method == "diff"
    initial_user_similarity_embedding = normalize(train(matrix_Train))
    
    # Get wanted items 
    candidate_items = matrix_Test[user].nonzero()[1]
    train_items = matrix_Train[user].nonzero()[1]
    wanted_items = np.setdiff1d(candidate_items, train_items)
    print ('wanted_items length: ',len(wanted_items))
    
    # Get initial forward prediction 
#     prediction_score,clf = get_initial_prediction(user, X = normalize(U_K.todense()), y = normalize(train(rtrain)),
#                             matrix_Train = rtrain, k = 100)
#     Y, RQt, Bias = plrec(matrix_Train,
#                     iteration = 10,
#                     lamb = 200,
#                     rank = 200)
#     RQ = RQt.T
#     reg = LinearRegression().fit(normalize(U_K), Y)
    prediction_score = predict_scores(matrix_U=RQ,
                                      matrix_V=Y,
                                      bias=Bias).T[user]
    # Get initial top recommended item(s)
    top_recommendations = np.argsort(prediction_score)[::-1]
    print ("Initial top recommendation index",top_recommendations[0])
    try:
        row['top_prediction_item_name'] = get_restaurant_name(df_train, business_df, top_recommendations[0])
    # in case we cannot get the restaurant name
    except: 
        row['top_prediction_item_name'] = 'CANNOT_FIND'
        print ('Cannot get restaurant name for ItemIndex: ', top_recommendations[0])
    
    
    # Get top recommended item's keyphrases
    top_item = top_recommendations[0] 
    top_recommend_keyphrases = get_valid_keyphrases(keyphrase_freq,
                                                    top_recommendations, 
                                                    item = top_item,
                                                    threshold=keyphrase_length_threshold,
                                                    mutiple_keyphrases_en = False, 
                                                    top_items = None)
    top_recommended_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = top_item)
    
    
    #####################################
    # For each item, do the critiquing
    
    #limit the item to only 10
    num_target_item = 0 # initialize item count
    
    for item in wanted_items:    
        print ('target_item: ', item)
        row['target_item'] = item
        try:
            row['item_name'] = get_restaurant_name(df_train, business_df, item)
        except:
            row['item_name'] = 'CANNOT_FIND'
            print ('Cannot get restaurant name for ItemIndex: ', item)

        # Get pre-critiquing rank
        initial_rank = np.where(item == np.argsort(prediction_score)[::-1])[0][0]
#         print ('target_item initial rank', int(initial_rank))
        row['pre_rank'] = int(initial_rank)

        # Get the target item's existing keyphrases
        item_keyphrases = keyphrase_freq[item].nonzero()[1]
        
        # For diff 
        target_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = item)
        diff_keyphrase_freq = target_keyphrase_freq - top_recommended_keyphrase_freq
        
        wanted_keyphrases_random = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
        wanted_keyphrases_pop = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
        wanted_keyphrases_diff = np.argsort(np.ravel(diff_keyphrase_freq))[::-1][:max_wanted_keyphrase]
        
        pruned_prediction_score = pruning(prediction_score, 
                                           wanted_keyphrases_random, 
                                           top_recommendations, 
                                           keyphrase_freq, 
                                           matrix_Train = rtrain)
        pure_pruning_rank = np.where(item == np.argsort(pruned_prediction_score)[::-1])[0][0]
        if pure_pruning_rank>initial_rank:
            pure_pruning_rank = initial_rank
        row['pure_pruning_rank'] = int(pure_pruning_rank)    
        
        affected_items = np.array([])
        modified_matrix = initial_user_similarity_embedding # initialize user similarity embedding
        
        #############################################
        # Critiquing iteration
        for iteration in range(max_iteration_threshold):
            print ('cur_iter ', iteration)
            row['iter'] = iteration

            if len(wanted_keyphrases_random) == 0 or len(wanted_keyphrases_diff) == 0: 
                print ('no more keyphrase available')
                break
            critiqued_keyphrase_random = np.random.choice(wanted_keyphrases_random, size=1, replace=False)[0]
            critiqued_keyphrase_pop = wanted_keyphrases_pop[np.argmin(keyphrase_popularity[wanted_keyphrases_pop])] # Select the least popular
            critiqued_keyphrase_diff = wanted_keyphrases_diff[0]
            
            row['critiqued_keyphrase_random'] = critiqued_keyphrase_random
            row['keyphrase_name_random'] = keyphrases[critiqued_keyphrase_random]
            row['critiqued_keyphrase_pop'] = critiqued_keyphrase_pop
            row['keyphrase_name_pop'] = keyphrases[critiqued_keyphrase_pop]
            row['critiqued_keyphrase_diff'] = critiqued_keyphrase_diff
            row['keyphrase_name_diff'] = keyphrases[critiqued_keyphrase_diff]
            
            # Do not critique this keyphrase next time
            wanted_keyphrases_random = np.delete(wanted_keyphrases_random, np.where(critiqued_keyphrase_random == wanted_keyphrases_random))
            wanted_keyphrases_pop = np.delete(wanted_keyphrases_pop, np.where(critiqued_keyphrase_pop == wanted_keyphrases_pop))
            wanted_keyphrases_diff = np.delete(wanted_keyphrases_diff, np.where(critiqued_keyphrase_diff == wanted_keyphrases_diff))
            
            # Critiquing Embedding

            # One hot encoding
            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_random)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_random =critiqued_matrix

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_pop)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_pop = critiqued_matrix

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_diff)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_diff = critiqued_matrix


            # Warning!!! The following is used only for testing single step critiquing, 
            # for full average critiquing, use the above commented line 
            post_ranks_random_all = []
            post_ranks_random_upper = []
            post_ranks_pop_all = []
            post_ranks_pop_upper = []
            post_ranks_diff_all = []
            post_ranks_diff_upper = []
            
            affected_items_random = keyphrase_freq[critiqued_keyphrase_random].nonzero()[0]
            affected_items_pop = keyphrase_freq[critiqued_keyphrase_pop].nonzero()[0]
            affected_items_diff = keyphrase_freq[critiqued_keyphrase_diff].nonzero()[0]
            
            for lam in lams:
                modified_matrix_random = (1-lam)*Y + lam*critiqued_matrix_random 
                modified_matrix_pop = (1-lam)*Y + lam*critiqued_matrix_pop 
                modified_matrix_diff = (1-lam)*Y + lam*critiqued_matrix_diff 
                
                # Random
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_random[user], 
                                     matrix_V=RQ,
                                     )
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_random_all.append(item_rank)
                # Random upper 
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_random[user], 
                                                     matrix_V=RQ, 
                                                     bias=None,
                                                   penalize = True,
                                                   keyphrase_freq = keyphrase_freq, 
                                                   critiqued_keyphrase = critiqued_keyphrase_random, 
                                                   matrix_Train = matrix_Train,
                                                   alpha = 0)
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_random_upper.append(item_rank)
                
                # Pop
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_pop[user], 
                                     matrix_V=RQ,
                                     )
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_pop_all.append(item_rank)
                # Pop upper 
                
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_pop[user], 
                                                     matrix_V=RQ, 
                                                     bias=None,
                                                   penalize = True,
                                                   keyphrase_freq = keyphrase_freq, 
                                                   critiqued_keyphrase = critiqued_keyphrase_pop, 
                                                   matrix_Train = matrix_Train,
                                                   alpha = 0)
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_pop_upper.append(item_rank)
                
                # Diff
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                     matrix_V=RQ,
                                     )
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_diff_all.append(item_rank)
                # Diff upper 
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                                     matrix_V=RQ, 
                                                     bias=None,
                                                   penalize = True,
                                                   keyphrase_freq = keyphrase_freq, 
                                                   critiqued_keyphrase = critiqued_keyphrase_diff, 
                                                   matrix_Train = matrix_Train,
                                                   alpha = 0)
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_diff_upper.append(item_rank)
                
                
            row['post_rank_random_all'] = post_ranks_random_all
            row['post_rank_random_upper'] = post_ranks_random_upper
            row['post_rank_pop_all'] = post_ranks_pop_all
            row['post_rank_pop_upper'] = post_ranks_pop_upper
            row['post_rank_diff_all'] = post_ranks_diff_all
            row['post_rank_diff_upper'] = post_ranks_diff_upper
            df = df.append(row, ignore_index=True)
            

        # break after got 10 target items 
        num_target_item += 1
        if num_target_item >10: # only want max 10 items per user
            break
            
    return df
Y, RQt, Bias = plrec(rtrain,
                    iteration = 10,
                    lamb = 200,
                    rank = 200)
RQ = RQt.T
reg = LinearRegression().fit(normalize(U_K), Y)<jupyter_output>Randomized SVD
Elapsed: 00:00:00
Closed-Form Linear Optimization
Elapsed: 00:00:00<jupyter_text>Setting Up Experiment<jupyter_code># keyphrase_popularity = get_keyphrase_popularity(df_train,keyphrases)

# Save and load
# np.savetxt('../data/yelp/'+'keyphrase_popularity.txt', keyphrase_popularity, fmt='%d')
keyphrase_popularity = np.loadtxt('../data/yelp/'+'keyphrase_popularity.txt', dtype=int)
business_df = get_business_df()
business_df.to_csv('../../data/yelp/business_df.csv')
# Initialize df for storing the experiment

# post_ranki is post rank with different lambda ratio for combining pre-post User similarity matrix 
columns = ['user_id', 'target_item', 'item_name', 'iter', 'pre_rank', 
           'top_prediction_item_name','critiqued_keyphrase', 'keyphrase_name', 
           'post_rank0', 
           'post_rank1', 
           'post_rank2', 
           'post_rank3', 
           'post_rank4', 
           'post_rank5', 
           'post_rank6', 
           'post_rank7', 
           'post_rank8',
           'post_rank9',
           'post_rank10',
           'num_existing_keyphrases'] 
df = pd.DataFrame(columns=columns)
row = {}<jupyter_output><empty_output><jupyter_text>Experiment Pipeline<jupyter_code>def single_step_critiquing_experiment_copy(user = 2, 
                           keyphrase_length_threshold = 150, 
                           max_iteration_threshold = 5,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity, 
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all',
                           lams = [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] 
                          ):
    """
    k: HR@k 
    keyphrase_length_threshold: limit the number of keyphrases in top recommended item
    keyphrase_selection_method: 'random': randomly select keyphrase from wanted_keyphrases
                                'pop': always select the most popular keyphrase in wanted_keyphrases
                                'diff': select the keyphrase with largest frequency difference between top recommended 
                                        item and target item.
    recommend_type: 'all': recommend all items
                    'upper' (only_with_critiqued_keyphrase): recommend items with only critiqued_keyphrase
    lam: modified_matrix = lam*origianl_matrix + (1-lam)*critiquing_embedding 
    """
    
    row['user_id'] = user
    print ('User ID ', user)
    
    # Set up (move to header line later)
    matrix_Train = rtrain
    matrix_Test = rtest
    keyphrase_freq = I_K
    num_items = rtrain.shape[1]
    max_wanted_keyphrase = 10 # for keyphrase_selection_method == "diff"
    initial_user_similarity_embedding = normalize(train(matrix_Train))
    
    # Get wanted items 
    candidate_items = matrix_Test[user].nonzero()[1]
    train_items = matrix_Train[user].nonzero()[1]
    wanted_items = np.setdiff1d(candidate_items, train_items)
    print ('wanted_items length: ',len(wanted_items))
    
    # Get initial forward prediction 
    prediction_score,clf = get_initial_prediction(user, X = normalize(U_K.todense()), y = normalize(train(rtrain)),
                            matrix_Train = rtrain, k = 100)
    
    # Get initial top recommended item(s)
    top_recommendations = np.argsort(prediction_score)[::-1]
    print ("Initial top recommendation index",top_recommendations[0])
    try:
        row['top_prediction_item_name'] = get_restaurant_name(df_train, business_df, top_recommendations[0])
    # in case we cannot get the restaurant name
    except: 
        row['top_prediction_item_name'] = 'CANNOT_FIND'
        print ('Cannot get restaurant name for ItemIndex: ', top_recommendations[0])
    
    
    # Get top recommended item's keyphrases
    top_item = top_recommendations[0] 
    top_recommend_keyphrases = get_valid_keyphrases(keyphrase_freq,
                                                    top_recommendations, 
                                                    item = top_item,
                                                    threshold=keyphrase_length_threshold,
                                                    mutiple_keyphrases_en = False, 
                                                    top_items = None)
    print ('num_top_recommended_keyphrases ',len(top_recommend_keyphrases))

    if keyphrase_selection_method == 'diff':
        top_recommended_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = top_item)
    
    
    #####################################
    # For each item, do the critiquing
    
    #limit the item to only 10
    num_target_item = 0 # initialize item count
    
    for item in wanted_items:    
        print ('target_item: ', item)
        row['target_item'] = item
        try:
            row['item_name'] = get_restaurant_name(df_train, business_df, item)
        except:
            row['item_name'] = 'CANNOT_FIND'
            print ('Cannot get restaurant name for ItemIndex: ', item)

        # Get pre-critiquing rank
        initial_rank = np.where(item == np.argsort(prediction_score)[::-1])[0][0]
#         print ('target_item initial rank', int(initial_rank))
        row['pre_rank'] = int(initial_rank)

        # Get the target item's existing keyphrases
        item_keyphrases = keyphrase_freq[item].nonzero()[1]
#         print ('num_existing_keyphrases ',len(item_keyphrases))
        
        if keyphrase_selection_method == 'diff':
            target_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = item)
            # indicate the keyphrase with large freq in target_item but small_keyphrase in top_recommended items
            diff_keyphrase_freq = target_keyphrase_freq - top_recommended_keyphrase_freq
            
        # Get wanted keyphrases
        if keyphrase_selection_method != 'diff':
            # Get keyphrases that is not in the top recommended items but in the target item (we can select)
            wanted_keyphrases = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)

            if len(wanted_keyphrases) == 0:
                print ("wanted_keyphrases is empty")
                break
            row['num_existing_keyphrases'] = len(wanted_keyphrases)
            
        # For 'diff'
        else:
            wanted_keyphrases = np.argsort(np.ravel(diff_keyphrase_freq))[::-1][:max_wanted_keyphrase]
            row['num_existing_keyphrases'] = len(wanted_keyphrases)

        affected_items = np.array([])
        modified_matrix = initial_user_similarity_embedding # initialize user similarity embedding
        
        #############################################
        # Critiquing iteration
        for iteration in range(max_iteration_threshold):
            print ('cur_iter ', iteration)
            row['iter'] = iteration
            if keyphrase_selection_method == 'random':
                # Randomly critique one keyphrase
                critiqued_keyphrase = np.random.choice(wanted_keyphrases, size=1, replace=False)[0]
            elif keyphrase_selection_method == 'pop':
                # Always critique the most popular keyphrase
                critiqued_keyphrase = wanted_keyphrases[np.argmax(keyphrase_popularity[wanted_keyphrases])]
            elif keyphrase_selection_method == 'diff':
                # critique the keyphrase with largest freq diff between top recommended_item and target_item
                critiqued_keyphrase = wanted_keyphrases[0]
#                 print (critiqued_keyphrase)
            
#             print ('critiqued_keyphrase ,',critiqued_keyphrase, keyphrases[critiqued_keyphrase])
            row['critiqued_keyphrase'] = critiqued_keyphrase
            row['keyphrase_name'] = keyphrases[critiqued_keyphrase]
            
            # Do not critique this keyphrase next time
            wanted_keyphrases = np.delete(wanted_keyphrases, np.where(critiqued_keyphrase == wanted_keyphrases))
            if len(wanted_keyphrases) == 0: 
                print ('no more keyphrase available')
                break
            
            # Get affected items (items have critiqued keyphrase)
            current_affected_items = keyphrase_freq[:, critiqued_keyphrase].nonzero()[0]
            affected_items = np.unique(np.concatenate((affected_items, current_affected_items))).astype(int) 
            unaffected_items = np.setdiff1d(range(num_items), affected_items)

            # Critiquing Embedding

            # One hot encoding
            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase)
            critiqued_matrix = clf.predict(critiqued_matrix_onehot)
            critiqued_matrix = normalize(critiqued_matrix)
            
#             critiqued_matrix = project_one_hot_encoding(clf, 
#                                                         U_K,
#                                                         user_index = user,
#                                                         critiqued_keyphrase = critiqued_keyphrase, 
#                                                         normalize_en = True)

#             modified_matrix = modified_matrix + critiqued_matrix # averaging user-item embedding and critiquing embeeding
            
            # Warning!!! The following is used only for testing single step critiquing, 
            # for full average critiquing, use the above commented line 
            post_ranks = []
            for lam in lams:
                modified_matrix = (1-lam)*normalize(train(matrix_Train)) + lam*critiqued_matrix 
#                 modified_matrix = normalize(modified_matrix)
                if lam == 0:
                    print (modified_matrix == initial_user_similarity_embedding)
            
                # Get new predictions from modified embedding
                if recommend_type == 'all':
                    prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix)
                if recommend_type == 'upper':
                    prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix, 
                                                         with_keyphrase = True, 
                                                         keyphrase_freq = keyphrase_freq, 
                                                         critiqued_keyphrase = critiqued_keyphrase, 
                                                         alpha = 0)
                post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
                print ('target_item post-critique rank with lambda '+str(lam), int(post_critique_rank))
                post_rank = int(post_critique_rank)
                post_ranks.append(post_rank)
            row['post_rank'] = post_ranks
            row['post_rank0'] = post_ranks[0]
            row['post_rank1'] = post_ranks[1]
            row['post_rank2'] = post_ranks[2]
            row['post_rank3'] = post_ranks[3]
            row['post_rank4'] = post_ranks[4]
            row['post_rank5'] = post_ranks[5]
            row['post_rank6'] = post_ranks[6]
            row['post_rank7'] = post_ranks[7]
            row['post_rank8'] = post_ranks[8]
            row['post_rank9'] = post_ranks[9]
            row['post_rank10'] = post_ranks[10]
            df = df.append(row, ignore_index=True)
            

        # break after got 10 target items 
        num_target_item += 1
        if num_target_item >10: # only want max 10 items per user
            break
            
    return df
def single_step_critiquing_experiment(user = 2, 
                           keyphrase_length_threshold = 150, 
                           max_iteration_threshold = 5,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity, 
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all',
                           lams = [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] 
                          ):
    """
    k: HR@k 
    keyphrase_length_threshold: limit the number of keyphrases in top recommended item
    keyphrase_selection_method: 'random': randomly select keyphrase from wanted_keyphrases
                                'pop': always select the most popular keyphrase in wanted_keyphrases
                                'diff': select the keyphrase with largest frequency difference between top recommended 
                                        item and target item.
    recommend_type: 'all': recommend all items
                    'upper' (only_with_critiqued_keyphrase): recommend items with only critiqued_keyphrase
    lam: modified_matrix = lam*origianl_matrix + (1-lam)*critiquing_embedding 
    """
    
    row['user_id'] = user
    print ('User ID ', user)
    
    # Set up (move to header line later)
    matrix_Train = rtrain
    matrix_Test = rtest
    keyphrase_freq = I_K
    num_items = rtrain.shape[1]
    max_wanted_keyphrase = 10 # for keyphrase_selection_method == "diff"
    initial_user_similarity_embedding = normalize(train(matrix_Train))
    
    # Get wanted items 
    candidate_items = matrix_Test[user].nonzero()[1]
    train_items = matrix_Train[user].nonzero()[1]
    wanted_items = np.setdiff1d(candidate_items, train_items)
    print ('wanted_items length: ',len(wanted_items))
    
    # Get initial forward prediction 
    prediction_score,clf = get_initial_prediction(user, X = normalize(U_K.todense()), y = normalize(train(rtrain)),
                            matrix_Train = rtrain, k = 100)
    
    # Get initial top recommended item(s)
    top_recommendations = np.argsort(prediction_score)[::-1]
    print ("Initial top recommendation index",top_recommendations[0])
    try:
        row['top_prediction_item_name'] = get_restaurant_name(df_train, business_df, top_recommendations[0])
    # in case we cannot get the restaurant name
    except: 
        row['top_prediction_item_name'] = 'CANNOT_FIND'
        print ('Cannot get restaurant name for ItemIndex: ', top_recommendations[0])
    
    
    # Get top recommended item's keyphrases
    top_item = top_recommendations[0] 
    top_recommend_keyphrases = get_valid_keyphrases(keyphrase_freq,
                                                    top_recommendations, 
                                                    item = top_item,
                                                    threshold=keyphrase_length_threshold,
                                                    mutiple_keyphrases_en = False, 
                                                    top_items = None)
    top_recommended_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = top_item)
    
    
    #####################################
    # For each item, do the critiquing
    
    #limit the item to only 10
    num_target_item = 0 # initialize item count
    
    for item in wanted_items:    
        print ('target_item: ', item)
        row['target_item'] = item
        try:
            row['item_name'] = get_restaurant_name(df_train, business_df, item)
        except:
            row['item_name'] = 'CANNOT_FIND'
            print ('Cannot get restaurant name for ItemIndex: ', item)

        # Get pre-critiquing rank
        initial_rank = np.where(item == np.argsort(prediction_score)[::-1])[0][0]
#         print ('target_item initial rank', int(initial_rank))
        row['pre_rank'] = int(initial_rank)

        # Get the target item's existing keyphrases
        item_keyphrases = keyphrase_freq[item].nonzero()[1]
#         print ('num_existing_keyphrases ',len(item_keyphrases))
        
#         if keyphrase_selection_method == 'diff':
#             target_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = item)
#             # indicate the keyphrase with large freq in target_item but small_keyphrase in top_recommended items
#             diff_keyphrase_freq = target_keyphrase_freq - top_recommended_keyphrase_freq
            
#         # Get wanted keyphrases
#         if keyphrase_selection_method != 'diff':
#             # Get keyphrases that is not in the top recommended items but in the target item (we can select)
#             wanted_keyphrases = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)

#             if len(wanted_keyphrases) == 0:
#                 print ("wanted_keyphrases is empty")
#                 break
#             row['num_existing_keyphrases'] = len(wanted_keyphrases)
            
#         # For 'diff'
#         else:
#             wanted_keyphrases = np.argsort(np.ravel(diff_keyphrase_freq))[::-1][:max_wanted_keyphrase]
#             row['num_existing_keyphrases'] = len(wanted_keyphrases)
        
        # For diff 
        target_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = item)
        diff_keyphrase_freq = target_keyphrase_freq - top_recommended_keyphrase_freq
        
        wanted_keyphrases_random = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
        wanted_keyphrases_pop = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
        wanted_keyphrases_diff = np.argsort(np.ravel(diff_keyphrase_freq))[::-1][:max_wanted_keyphrase]
        
            
        affected_items = np.array([])
        modified_matrix = initial_user_similarity_embedding # initialize user similarity embedding
        
        #############################################
        # Critiquing iteration
        for iteration in range(max_iteration_threshold):
            print ('cur_iter ', iteration)
            row['iter'] = iteration
            
#             if keyphrase_selection_method == 'random':
#                 # Randomly critique one keyphrase
#                 critiqued_keyphrase = np.random.choice(wanted_keyphrases, size=1, replace=False)[0]
#             elif keyphrase_selection_method == 'pop':
#                 # Always critique the most popular keyphrase
#                 critiqued_keyphrase = wanted_keyphrases[np.argmax(keyphrase_popularity[wanted_keyphrases])]
#             elif keyphrase_selection_method == 'diff':
#                 # critique the keyphrase with largest freq diff between top recommended_item and target_item
#                 critiqued_keyphrase = wanted_keyphrases[0]
            print (wanted_keyphrases_random)
            if len(wanted_keyphrases_random) == 0 or len(wanted_keyphrases_diff) == 0: 
                print ('no more keyphrase available')
                break
            critiqued_keyphrase_random = np.random.choice(wanted_keyphrases_random, size=1, replace=False)[0]
            critiqued_keyphrase_pop = wanted_keyphrases_pop[np.argmin(keyphrase_popularity[wanted_keyphrases_pop])] # Select the least popular
            critiqued_keyphrase_diff = wanted_keyphrases_diff[0]
            
            row['critiqued_keyphrase_random'] = critiqued_keyphrase_random
            row['keyphrase_name_random'] = keyphrases[critiqued_keyphrase_random]
            row['critiqued_keyphrase_pop'] = critiqued_keyphrase_pop
            row['keyphrase_name_pop'] = keyphrases[critiqued_keyphrase_pop]
            row['critiqued_keyphrase_diff'] = critiqued_keyphrase_diff
            row['keyphrase_name_diff'] = keyphrases[critiqued_keyphrase_diff]
            
            # Do not critique this keyphrase next time
            wanted_keyphrases_random = np.delete(wanted_keyphrases_random, np.where(critiqued_keyphrase_random == wanted_keyphrases_random))
            wanted_keyphrases_pop = np.delete(wanted_keyphrases_pop, np.where(critiqued_keyphrase_pop == wanted_keyphrases_pop))
            wanted_keyphrases_diff = np.delete(wanted_keyphrases_diff, np.where(critiqued_keyphrase_diff == wanted_keyphrases_diff))
            
            
            # Get affected items (items have critiqued keyphrase)
#             current_affected_items = keyphrase_freq[:, critiqued_keyphrase].nonzero()[0]
#             affected_items = np.unique(np.concatenate((affected_items, current_affected_items))).astype(int) 
#             unaffected_items = np.setdiff1d(range(num_items), affected_items)

            # Critiquing Embedding

            # One hot encoding
            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_random)
            critiqued_matrix = clf.predict(critiqued_matrix_onehot)
            critiqued_matrix_random = normalize(critiqued_matrix)

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_pop)
            critiqued_matrix = clf.predict(critiqued_matrix_onehot)
            critiqued_matrix_pop = normalize(critiqued_matrix)

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_diff)
            critiqued_matrix = clf.predict(critiqued_matrix_onehot)
            critiqued_matrix_diff = normalize(critiqued_matrix)


#             critiqued_matrix = project_one_hot_encoding(clf, 
#                                                         U_K,
#                                                         user_index = user,
#                                                         critiqued_keyphrase = critiqued_keyphrase, 
#                                                         normalize_en = True)

#             modified_matrix = modified_matrix + critiqued_matrix # averaging user-item embedding and critiquing embeeding
            
            # Warning!!! The following is used only for testing single step critiquing, 
            # for full average critiquing, use the above commented line 
            post_ranks_random_all = []
            post_ranks_random_upper = []
            post_ranks_pop_all = []
            post_ranks_pop_upper = []
            post_ranks_diff_all = []
            post_ranks_diff_upper = []
            
            for lam in lams:
                modified_matrix_random = (1-lam)*normalize(train(matrix_Train)) + lam*critiqued_matrix_random 
                modified_matrix_pop = (1-lam)*normalize(train(matrix_Train)) + lam*critiqued_matrix_pop 
                modified_matrix_diff = (1-lam)*normalize(train(matrix_Train)) + lam*critiqued_matrix_diff 
#                 modified_matrix = normalize(modified_matrix)

            
                # Get new predictions from modified embedding
#                 if recommend_type == 'all':
#                     prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix)
#                 if recommend_type == 'upper':
#                     prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix, 
#                                                          with_keyphrase = True, 
#                                                          keyphrase_freq = keyphrase_freq, 
#                                                          critiqued_keyphrase = critiqued_keyphrase, 
#                                                          alpha = 0)
#                 post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
#                 print ('target_item post-critique rank with lambda '+str(lam), int(post_critique_rank))
#                 post_rank = int(post_critique_rank)
#                 post_ranks.append(post_rank)
                
                
                 
                #Pop
                prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix_pop)
                post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
                post_ranks_pop_all.append(post_critique_rank)
            
                prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix_pop, 
                                                         with_keyphrase = True, 
                                                         keyphrase_freq = keyphrase_freq, 
                                                         critiqued_keyphrase = critiqued_keyphrase_pop, 
                                                         alpha = 0)
                post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
                post_ranks_pop_upper.append(post_critique_rank)
                
                #Diff
                prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix_diff)
                post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
                post_ranks_diff_all.append(post_critique_rank)
            
                prediction_scores_u = predict_vector(user, matrix_Train, 100, modified_matrix_diff, 
                                                         with_keyphrase = True, 
                                                         keyphrase_freq = keyphrase_freq, 
                                                         critiqued_keyphrase = critiqued_keyphrase_diff, 
                                                         alpha = 0)
                post_critique_rank = np.where(item == np.argsort(prediction_scores_u)[::-1])[0][0]
                post_ranks_diff_upper.append(post_critique_rank)
            
            row['post_rank_random_all'] = post_ranks_random_all
            row['post_rank_random_upper'] = post_ranks_random_upper
            row['post_rank_pop_all'] = post_ranks_pop_all
            row['post_rank_pop_upper'] = post_ranks_pop_upper
            row['post_rank_diff_all'] = post_ranks_diff_all
            row['post_rank_diff_upper'] = post_ranks_diff_upper
            df = df.append(row, ignore_index=True)
            

        # break after got 10 target items 
        num_target_item += 1
        if num_target_item >10: # only want max 10 items per user
            break
            
    return df<jupyter_output><empty_output><jupyter_text>Run Experiment<jupyter_code>single_step_with_avg_path = "../tables/critiquing/plrec_with_obj/"
# KNN

# post_ranki is post rank with different lambda ratio for combining pre-post User similarity matrix 
columns = ['user_id', 'target_item', 'item_name', 'iter', 'pre_rank', 
           'top_prediction_item_name',
           'post_rank_random_all',
           'post_rank_random_upper',
           'post_rank_pop_all',
           'post_rank_pop_upper',
           'post_rank_diff_all',
           'post_rank_diff_upper',
           'critiqued_keyphrase_random',
           'keyphrase_name_random',
           'critiqued_keyphrase_pop',
           'keyphrase_name_pop',
           'critiqued_keyphrase_diff',
           'keyphrase_name_diff',
           'num_existing_keyphrases'] 
df = pd.DataFrame(columns=columns)
row = {}

#only_with_critiqued_keyphrase
for user in range(300,350):
    df = single_step_critiquing_experiment(user = user, 
                           keyphrase_length_threshold = 230, 
                           max_iteration_threshold = 10,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity,
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all'
                           )
df.to_csv(single_step_with_avg_path+"50user.csv")
# PLREC
columns = ['user_id', 'target_item', 'item_name', 'iter', 'pre_rank', 
           'top_prediction_item_name',
           'post_rank_random_all',
           'post_rank_random_upper',
           'post_rank_pop_all',
           'post_rank_pop_upper',
           'post_rank_diff_all',
           'post_rank_diff_upper',
           'critiqued_keyphrase_random',
           'keyphrase_name_random',
           'critiqued_keyphrase_pop',
           'keyphrase_name_pop',
           'critiqued_keyphrase_diff',
           'keyphrase_name_diff',
           'num_existing_keyphrases',
           'pure_pruning_rank'] 
df = pd.DataFrame(columns=columns)
row = {}

#only_with_critiqued_keyphrase
for user in range(300,301):
    df = single_step_critiquing_plrec(user = user, 
                           keyphrase_length_threshold = 230, 
                           max_iteration_threshold = 10,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity,
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all'
                           )
df.to_csv(single_step_with_avg_path+"test.csv")
df = pd.read_csv(single_step_with_avg_path+"25user.csv")
plot_lambda_vs_performance(df, k = 30, show = True, savefig = False)<jupyter_output><empty_output><jupyter_text>Visualization   Load experiment res<jupyter_code>df = pd.read_csv(single_step_with_avg_path+"50user.csv")
df
literal_eval(df['post_rank_random_all'][0])
df['post_rank_random_all'].values.tolist()<jupyter_output><empty_output><jupyter_text>Plot HR vs. Performance for 3 keyphrase selection method<jupyter_code># Get post rank hr_k with different lambda values
def get_post_rank(df,rank_type = 'random_all', line = 0):
    """
    get one line of the post_rank res
    """
    return np.array(literal_eval(df['post_rank_'+rank_type][line])).reshape(1,11)

def concat_rank(df,rank_type = 'random_all'):
    """
    concat all post_rank res to one matrix
    """
    res = get_post_rank(df, rank_type=rank_type, line = 0)
    for i in range(1,len(df)):
        res = np.concatenate((res,get_post_rank(df, rank_type=rank_type, line = i)))
    return res

def get_post_hr_at_k(df, k, rank_type = 'random_all'):
    """
    Get hr_at_k for rank_type
    return a numpy array of length 11 corresponding to each lambda value 
    """
    ranks = concat_rank(df,rank_type = rank_type) # concat the experiment result for rank_type

    hr_at_k = np.where(ranks<k)[1] # compare rank with k
    count = np.unique(hr_at_k, return_counts=True) # get count for each lambda value

    return count[1]/len(df) 

def get_pre_hr_at_k(df, k, pre_col = 'pre_rank'):
    pre_hit = len(np.where(df[pre_col]<k)[0])
    return pre_hit/len(df)
def plot_lambda_vs_performance(df, k = 25, show = True, savefig = False):
    """get hr@k graph for all 6 options
    x_axis: lambda from all initial to all modified matrix
    y_axis: performance (hr@k)"""
    plt.figure(figsize=(12,12))
    # Get lines
    pre_hr = [get_pre_hr_at_k(df,k,pre_col = 'pre_rank')]*11 # repeat len(lams) times
    pruned_hr = [get_pre_hr_at_k(df,k,pre_col = 'pure_pruning_rank')]*11 # repeat len(lams) times
    random_all = get_post_hr_at_k(df,k,rank_type='random_all')
    random_upper = get_post_hr_at_k(df,k,rank_type='random_upper')
    pop_all = get_post_hr_at_k(df,k,rank_type='pop_all')
    pop_upper = get_post_hr_at_k(df,k,rank_type='pop_upper')
    diff_all = get_post_hr_at_k(df,k,rank_type='diff_all')
    diff_upper = get_post_hr_at_k(df,k,rank_type='diff_upper')

    plt.plot(np.arange(11), pre_hr,'b')
    plt.plot(np.arange(11), pruned_hr,'b--')
    plt.plot(np.arange(11), random_all,'r-')
    plt.plot(np.arange(11), random_upper,'r--')
    plt.plot(np.arange(11), pop_all,'y-')
    plt.plot(np.arange(11), pop_upper,'y--')
    plt.plot(np.arange(11), diff_all,'g-')
    plt.plot(np.arange(11), diff_upper,'g--')

    x = [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]
    xi = list(range(len(x)))
    # plt.xticks(np.arange(len(k_list)), k_list)
    plt.xlabel('lambda')
    plt.ylabel('HR@K')
    plt.legend(['Pre-Critiquing', 'Pure-pruning','Random', 'Random_Upper','Pop','Pop_Upper','Diff','Diff_Upper'])
    plt.xticks(xi, x)
    if show == True:
        plt.show()
    if savefig == True:
        plt.savefig('../figs/three_keyphrase_selection_methods_with_upper_bound_0115_hr15')
plot_lambda_vs_performance(df, k = 25)
plot_lambda_vs_performance(df, k = 15)



def hr_at_k(df, k):
    pre_hit = np.where(df['pre_rank']<k)[0]
    post_hit = np.where(df['post_rank']<k)[0]


def hr_at_k(df, k):
    """
    Given the above dataframe, calculate the avg pre and post hit rate at k 
    """
    pre_hit = np.where(df['pre_rank']<k)[0]
    post_hit = np.where(df['post_rank']<k)[0]
    pre_hr = len(pre_hit)/len(df)
    post_hr = len(post_hit)/len(df)
    return pre_hr, post_hr
# Utlilty func
def hr_at_k(df, k):
    """
    Given the above dataframe, calculate the avg pre and post hit rate at k 
    """
    pre_hit = np.where(df['pre_rank']<k)[0]
    post_hit = np.where(df['post_rank']<k)[0]
    pre_hr = len(pre_hit)/len(df)
    post_hr = len(post_hit)/len(df)
    return pre_hr, post_hr

def get_hr(l=5, rang = 200):
    """
    Get the hit rate at different rang, with different lambda value
    Output in the form of list
    """
    pre_hr_list = []
    post_hr_list = []
    for k in range(1,rang):
        pre_hr,post_hr = hr_at_k(df,4, k)
        pre_hr_list.append(pre_hr)
        post_hr_list.append(post_hr)
    return pre_hr_list,post_hr_list

def get_hr_of_all_lambda(methods):
    for method in methods:
        post_rates = []
        for i in range(9):
            _,a = get_hr(l=i)
            diff.append(a)
pre_hr_list,b = get_hr(l=5)
def plot_hr_performance():
    plt.figure(figsize=(12,12))
    for 
    plt.plot(np.arange(len(pre_hr_list)), pre_hr_list)
    plt.plot(np.arange(len(pre_hr_list)), a1)
    plt.plot(np.arange(len(pre_hr_list)), a2)
    plt.plot(np.arange(len(pre_hr_list)), a3)
    plt.plot(np.arange(len(pre_hr_list)), a4)
    plt.plot(np.arange(len(pre_hr_list)), a5)
    plt.plot(np.arange(len(pre_hr_list)), a6)

    # plt.xticks(np.arange(len(k_list)), k_list)
    plt.xlabel('k')
    plt.ylabel('HR@K')
    plt.legend(['Pre-Critiquing','Random', 'Random_Upper','Pop','Pop_Upper','Diff','Diff_Upper'])
    plt.show()
    plt.savefig('../figs/three_keyphrase_selection_methods_with_upper_bound_0104')<jupyter_output><empty_output><jupyter_text>1D Optimization  Examples<jupyter_code># 1d un-constrained
# single variable, unconstrained minimization problem
import scipy.optimize as opt

objective = np.poly1d([1.0, -2.0, 0.0])
print(objective)
x0 = 3.0
results = opt.minimize(objective,x0)
print("Solution: x=%f" % results.x)
import matplotlib.pylab as plt
x = np.linspace(-3,5,100)
plt.plot(x,objective(x))
plt.plot(results.x,objective(results.x),'ro')
plt.show()
objective = np.poly1d([1.0, -2.0, 0.0])
cons = ({'type': 'ineq',
'fun' : lambda x: np.array([x[0] - 2])})
results = opt.minimize(objective,x0=3.0,
constraints = cons,
options = {'disp':True})
results.x
objective(results.x)
x = np.linspace(-3,5,100)
plt.plot(x,objective(x))
plt.plot(results.x,objective(results.x),'ro')
plt.show()
# One hot encoding
            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_random)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_random =critiqued_matrix

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_pop)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_pop = critiqued_matrix

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_diff)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_diff = critiqued_matrix


            # Warning!!! The following is used only for testing single step critiquing, 
            # for full average critiquing, use the above commented line 
            post_ranks_random_all = []
            post_ranks_random_upper = []
            post_ranks_pop_all = []
            post_ranks_pop_upper = []
            post_ranks_diff_all = []
            post_ranks_diff_upper = []
            
            affected_items_random = keyphrase_freq[critiqued_keyphrase_random].nonzero()[0]
            affected_items_pop = keyphrase_freq[critiqued_keyphrase_pop].nonzero()[0]
            affected_items_diff = keyphrase_freq[critiqued_keyphrase_diff].nonzero()[0]
            
            for lam in lams:
                modified_matrix_random = (1-lam)*Y + lam*critiqued_matrix_random 
                modified_matrix_pop = (1-lam)*Y + lam*critiqued_matrix_pop 
                modified_matrix_diff = (1-lam)*Y + lam*critiqued_matrix_diff 
                
                # Random
                prediction_scores_u = predict_scores(matrix_U=modified_matrix_random[user], 
                                     matrix_V=RQ,
                                     )
                prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                                  train_vector=matrix_Train[user],
                                                  remove_train=False)
                
                item_rank = np.where(prediction_items == item)[0][0]
                post_ranks_random_all.append(item_rank)<jupyter_output><empty_output><jupyter_text>Objective<jupyter_code># minimize w1 *  abs(lambda*[U_V_critique - U_V_initial] * V_I ) <for unaffected items> 
# + w2* (lambda*[U_V_critique - U_V_initial] * V_I ) <for affected items>
critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_random)
critiqued_matrix = reg.predict(critiqued_matrix_onehot)
critiqued_matrix_random =critiqued_matrix

U_V_critique = critiqued_matrix_random
U_V_initial = Y
latent_diff = U_V_critique - U_V_initial

rating_diff = predict_scores(matrix_U=lamb * latent_diff[user], 
                     matrix_V=RQ)
rating_diff_sum_unaffected = np.sum(abs(rating_diff), where = np.in1d(range(num_items),unaffected_items))
rating_diff_sum_affected = np.sum(rating_diff, where = np.in1d(range(num_items),affected_items))
obj = w1*abs(rating_diff_sum_unaffected) + w2*rating_diff_sum_affected 


a = np.array([-1,2,-3])
abs(a)
critiqued_matrix_onehot = get_critiqued_UK(U_K, 0, 0)
critiqued_matrix = reg.predict(critiqued_matrix_onehot)
critiqued_matrix_random =critiqued_matrix
critiqued_matrix_random.shape
U_V_critique = critiqued_matrix_random
U_V_initial = Y
latent_diff = U_V_initial - U_V_critique 
latent_diff
count = 0
for i in range(len(rating_diff)):
    if rating_diff[i] < 0:
        count += 1
count
sum(rating_diff)
lamb = 0.4
w1 = 0.1 * 1e-5
w2 = 0.9 * 1e-5
# rating_diff = predict_scores(matrix_U=lamb * latent_diff[0], 
#                      matrix_V=RQ)
rating_diff = predict_scores(matrix_U=lamb * latent_diff[0], 
                     matrix_V=RQ)

rating_diff_sum_unaffected = np.sum(abs(rating_diff), where = np.in1d(range(num_items),unaffected_items))
rating_diff_sum_affected = np.sum(rating_diff, where = np.in1d(range(num_items),affected_items))
obj = w1*abs(rating_diff_sum_unaffected) + w2*rating_diff_sum_affected 
obj*1e3
def obj(lamb):
    w1 = 1 * 1e-5
    w2 = 4 * 1e-5
    rating_diff = predict_scores(matrix_U=lamb * latent_diff[0], 
                     matrix_V=RQ)

#     rating_diff_sum_unaffected = np.sum(abs(rating_diff), where = np.in1d(range(num_items),unaffected_items))
    rating_diff_sum_unaffected = np.sum(np.square(rating_diff), where = np.in1d(range(num_items),unaffected_items))
    rating_diff_sum_affected = np.sum(rating_diff, where = np.in1d(range(num_items),affected_items))
    obj = w1*abs(rating_diff_sum_unaffected) + w2*rating_diff_sum_affected 
    return obj*1e3

x0 = np.array([0.5])
bds = ((0,1),)
sol = minimize(obj, x0, bounds = bds)
# Get affected and unaffected items location
np.in1d(range(num_items),affected_items)
a = np.array([1,3,4,5,13,4,3])
np.sum(a,where = [True, False, False, True,False,False,False])
# Affected Items
np.where()
I_K[:,0].nonzero()[0]
def predict_vector(rating_vector, train_vector, remove_train=True):
    dim = len(rating_vector)
    candidate_index = np.argpartition(-rating_vector, dim-1)[:dim]
    prediction_items = candidate_index[rating_vector[candidate_index].argsort()[::-1]]
    
    if remove_train:
        return np.delete(prediction_items, np.isin(prediction_items, train_vector.nonzero()[1]).nonzero()[0])
    else:
        return prediction_items    
prediction_items = predict_vector(rating_vector=prediction_scores[user],
                                  train_vector=matrix_Train[user],
                                  remove_train=True)
affected_items_mask = np.in1d(prediction_items, affected_items)
affected_items_index_rank = np.where(affected_items_mask == True)
unaffected_items_index_rank = np.where(affected_items_mask == False)
prediction_score = predict_scores(matrix_U=RQ,
                                      matrix_V=Y,
                                      bias=Bias).T[0]
prediction_score
# Initial Prediction
prediction_items = predict_vector(rating_vector=prediction_score,
                                  train_vector=rtrain[0],
                                  remove_train=True)
critiqued_keyphrase = 0

# Get affected and unaffected items from keyphrase selection
affected_items = I_K[:, critiqued_keyphrase].nonzero()[0]
num_items = rtrain.shape[1]
unaffected_items = np.setdiff1d(range(num_items), affected_items)

affected_items_mask = np.in1d(prediction_items, affected_items) 
affected_items_index_rank = np.where(affected_items_mask == True) # Get items in top recommendation that have been affected
unaffected_items_index_rank = np.where(affected_items_mask == False) # Get  items in top recommendation that are unaffected

np.intersect1d(affected_items, prediction_items[affected_items_index_rank[0][:10]])
np.intersect1d(unaffected_items, prediction_items[unaffected_items_index_rank[0][:10]])
W2 = reg.coef_
user_vector = rtrain[0]
W = W2.T.dot(user_vector.T)
def single_step_critiquing_plrec(user = 2, 
                           keyphrase_length_threshold = 150, 
                           max_iteration_threshold = 5,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity, 
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all',
                           reg = reg, Y = Y, RQt = RQt, Bias = Bias,
                            w1 = 1 * 1e-5, w2 = 4 * 1e-5 
                          ):
    """
    k: HR@k 
    keyphrase_length_threshold: limit the number of keyphrases in top recommended item
    keyphrase_selection_method: 'random': randomly select keyphrase from wanted_keyphrases
                                'pop': always select the most popular keyphrase in wanted_keyphrases
                                'diff': select the keyphrase with largest frequency difference between top recommended 
                                        item and target item.
    recommend_type: 'all': recommend all items
                    'upper' (only_with_critiqued_keyphrase): recommend items with only critiqued_keyphrase
    lam: modified_matrix = lam*origianl_matrix + (1-lam)*critiquing_embedding 
    """
    
    row['user_id'] = user
    print ('User ID ', user)
    
    # Set up (move to header line later)
    matrix_Train = rtrain
    matrix_Test = rtest
    keyphrase_freq = I_K
    num_items = rtrain.shape[1]
    max_wanted_keyphrase = 10 # for keyphrase_selection_method == "diff"
    initial_user_similarity_embedding = normalize(train(matrix_Train))
    
    # Get wanted items 
    candidate_items = matrix_Test[user].nonzero()[1]
    train_items = matrix_Train[user].nonzero()[1]
    wanted_items = np.setdiff1d(candidate_items, train_items)
    print ('wanted_items length: ',len(wanted_items))

    prediction_score = predict_scores(matrix_U=RQ,
                                      matrix_V=Y,
                                      bias=Bias).T[user]
    # Get initial top recommended item(s)
    top_recommendations = np.argsort(prediction_score)[::-1]
    print ("Initial top recommendation index",top_recommendations[0])
    try:
        row['top_prediction_item_name'] = get_restaurant_name(df_train, business_df, top_recommendations[0])
    # in case we cannot get the restaurant name
    except: 
        row['top_prediction_item_name'] = 'CANNOT_FIND'
        print ('Cannot get restaurant name for ItemIndex: ', top_recommendations[0])
    
    
    # Get top recommended item's keyphrases
    top_item = top_recommendations[0] 
    top_recommend_keyphrases = get_valid_keyphrases(keyphrase_freq,
                                                    top_recommendations, 
                                                    item = top_item,
                                                    threshold=keyphrase_length_threshold,
                                                    mutiple_keyphrases_en = False, 
                                                    top_items = None)
    top_recommended_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = top_item)
    
    
    #####################################
    # For each item, do the critiquing
    
    #limit the item to only 10
    num_target_item = 0 # initialize item count
    
    for item in wanted_items:    
        print ('target_item: ', item)
        row['target_item'] = item
        try:
            row['item_name'] = get_restaurant_name(df_train, business_df, item)
        except:
            row['item_name'] = 'CANNOT_FIND'
            print ('Cannot get restaurant name for ItemIndex: ', item)

        # Get pre-critiquing rank
        initial_rank = np.where(item == np.argsort(prediction_score)[::-1])[0][0]
#         print ('target_item initial rank', int(initial_rank))
        row['pre_rank'] = int(initial_rank)

        # Get the target item's existing keyphrases
        item_keyphrases = keyphrase_freq[item].nonzero()[1]
        
        # For diff 
        target_keyphrase_freq = get_item_keyphrase_freq(keyphrase_freq,item = item)
        diff_keyphrase_freq = target_keyphrase_freq - top_recommended_keyphrase_freq
        
        wanted_keyphrases_random = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
#         wanted_keyphrases_pop = np.setdiff1d(item_keyphrases,top_recommend_keyphrases)
        wanted_keyphrases_diff = np.argsort(np.ravel(diff_keyphrase_freq))[::-1][:max_wanted_keyphrase]
        
        
        pruned_prediction_score = pruning(prediction_score, 
                                           wanted_keyphrases_random, 
                                           top_recommendations, 
                                           keyphrase_freq, 
                                           matrix_Train = rtrain)
        pure_pruning_rank = np.where(item == np.argsort(pruned_prediction_score)[::-1])[0][0]
        if pure_pruning_rank>initial_rank:
            pure_pruning_rank = initial_rank
        row['pure_pruning_rank'] = int(pure_pruning_rank)    
        
        affected_items = np.array([])
        modified_matrix = initial_user_similarity_embedding # initialize user similarity embedding
        
        #############################################
        # Critiquing iteration
        for iteration in range(max_iteration_threshold):
            print ('cur_iter ', iteration)
            row['iter'] = iteration

#             if len(wanted_keyphrases_random) == 0 or len(wanted_keyphrases_diff) == 0: 
#                 print ('no more keyphrase available')
#                 break
#             critiqued_keyphrase_random = np.random.choice(wanted_keyphrases_random, size=1, replace=False)[0]
#             critiqued_keyphrase_pop = wanted_keyphrases_pop[np.argmin(keyphrase_popularity[wanted_keyphrases_pop])] # Select the least popular
            critiqued_keyphrase_diff = wanted_keyphrases_diff[0]
            
#             row['critiqued_keyphrase_random'] = critiqued_keyphrase_random
#             row['keyphrase_name_random'] = keyphrases[critiqued_keyphrase_random]
#             row['critiqued_keyphrase_pop'] = critiqued_keyphrase_pop
#             row['keyphrase_name_pop'] = keyphrases[critiqued_keyphrase_pop]
            row['critiqued_keyphrase_diff'] = critiqued_keyphrase_diff
            row['keyphrase_name_diff'] = keyphrases[critiqued_keyphrase_diff]
            
            # Do not critique this keyphrase next time
#             wanted_keyphrases_random = np.delete(wanted_keyphrases_random, np.where(critiqued_keyphrase_random == wanted_keyphrases_random))
#             wanted_keyphrases_pop = np.delete(wanted_keyphrases_pop, np.where(critiqued_keyphrase_pop == wanted_keyphrases_pop))
            wanted_keyphrases_diff = np.delete(wanted_keyphrases_diff, np.where(critiqued_keyphrase_diff == wanted_keyphrases_diff))
            
            # Critiquing Embedding

            # One hot encoding
#             critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_random)
#             critiqued_matrix = reg.predict(critiqued_matrix_onehot)
#             critiqued_matrix_random =critiqued_matrix

#             critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_pop)
#             critiqued_matrix = reg.predict(critiqued_matrix_onehot)
#             critiqued_matrix_pop = critiqued_matrix

            critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase_diff)
            critiqued_matrix = reg.predict(critiqued_matrix_onehot)
            critiqued_matrix_diff = critiqued_matrix


            # Warning!!! The following is used only for testing single step critiquing, 
            # for full average critiquing, use the above commented line 
#             post_ranks_random_all = []
#             post_ranks_random_upper = []
#             post_ranks_pop_all = []
#             post_ranks_pop_upper = []
            post_ranks_diff_all = []
            post_ranks_diff_upper = []
            
#             affected_items_random = keyphrase_freq[critiqued_keyphrase_random].nonzero()[0]
#             affected_items_pop = keyphrase_freq[critiqued_keyphrase_pop].nonzero()[0]
#             affected_items_diff = keyphrase_freq[critiqued_keyphrase_diff].nonzero()[0]
            
    
#                 modified_matrix_random = (1-lam)*Y + lam*critiqued_matrix_random 
#                 modified_matrix_pop = (1-lam)*Y + lam*critiqued_matrix_pop 
            
    
            # Get Optimal Lambda
            lam = Object1(user, critiqued_keyphrase_diff, I_K, U_K, reg, w1 = w1, w2 = w2)
            print ('Optimal Lambda value: ',lam)
            modified_matrix_diff = (1-lam)*Y + lam*critiqued_matrix_diff 
            
                
            # Diff
            prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                 matrix_V=RQ,
                                 )
            prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                              train_vector=matrix_Train[user],
                                              remove_train=False)

            item_rank = np.where(prediction_items == item)[0][0]
            post_ranks_diff_all.append(item_rank)
            # Diff upper 
            prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                                 matrix_V=RQ, 
                                                 bias=None,
                                               penalize = True,
                                               keyphrase_freq = keyphrase_freq, 
                                               critiqued_keyphrase = critiqued_keyphrase_diff, 
                                               matrix_Train = matrix_Train,
                                               alpha = 0)
            prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                              train_vector=matrix_Train[user],
                                              remove_train=False)
            item_rank = np.where(prediction_items == item)[0][0]
            post_ranks_diff_upper.append(item_rank)
                
                
#             row['post_rank_random_all'] = post_ranks_random_all
#             row['post_rank_random_upper'] = post_ranks_random_upper
#             row['post_rank_pop_all'] = post_ranks_pop_all
#             row['post_rank_pop_upper'] = post_ranks_pop_upper
            row['post_rank_diff_all'] = post_ranks_diff_all
            row['post_rank_diff_upper'] = post_ranks_diff_upper
            df = df.append(row, ignore_index=True)
            

        # break after got 10 target items 
        num_target_item += 1
        if num_target_item >10: # only want max 10 items per user
            break
            
    return df
def Object1(user, critiqued_keyphrase, I_K, U_K, reg, w1 = 1 * 1e-5, w2 = 4 * 1e-5):
    """
    Minimize w1*abs(post_rating - pre_rating) + w2(post_rating - pre_rating)
    Constraint: 0 < lambda < 1
    where w1/w2 = items in critiqued list / items in non-critiqued list
    Optimal Critiqued Vector  =  (1 - lambda) * initial + lambda * modified
    """
#     # Try equal weighting first
#     w1 = 0.5
#     w2 = 0.5
#     # Try different weighting later
#     num_items = I_K.shape[0]
    affected_items = I_K.T[critiqued_keyphrase].nonzero()[1]
    unaffected_items = np.setdiff1d(range(num_items), affected_items)
#     num_affected_items = int(affected_items.shape[0])
#     num_unaffected_items = int(unaffected_items.shape[0])
#     w1 = num_unaffected_items/(num_affected_items+num_unaffected_items) # TODO: Check this
#     w2 = 1-w1
    
    # Pre-calculation for LP 
    critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase)
    critiqued_matrix = reg.predict(critiqued_matrix_onehot)

    U_V_critique = critiqued_matrix
    U_V_initial = Y
    latent_diff = U_V_critique - U_V_initial
    
    
    # Setting up objective function
    def objective(lamb, latent_diff = latent_diff):
#         w1 = 1 * 1e-5
#         w2 = 4 * 1e-5
        rating_diff = predict_scores(matrix_U=lamb * latent_diff[0], 
                     matrix_V=RQ)

    #     rating_diff_sum_unaffected = np.sum(abs(rating_diff), where = np.in1d(range(num_items),unaffected_items))
        rating_diff_sum_unaffected = np.sum(np.square(rating_diff), where = np.in1d(range(num_items),unaffected_items))
        rating_diff_sum_affected = np.sum(rating_diff, where = np.in1d(range(num_items),affected_items))
        res = w1*abs(rating_diff_sum_unaffected) + w2*rating_diff_sum_affected 
        print (res*1e3)
        return res*1e3
    x0 = np.array([0.5])
    bds = ((0,1),) # Constraint on lambda
    sol = minimize(objective, x0, bounds = bds)
    optimal_lamb = sol.x
    return optimal_lamb
critiqued_matrix_onehot = get_critiqued_UK(U_K,0,0)
critiqued_matrix = reg.predict(critiqued_matrix_onehot)
critiqued_matrix_diff = critiqued_matrix
lam = 0
modified_matrix_diff = (1-lam)*Y + lam*critiqued_matrix_diff 
prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[0], 
                                 matrix_V=RQ,
                                 )
prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                              train_vector=rtrain[0],
                                              remove_train=True)
prediction_items[:10]
affected_items = I_K.T[0].nonzero()[1]
unaffected_items = np.setdiff1d(range(num_items), affected_items)
affected_items
np.in1d(prediction_items[:10], affected_items)
def Object2(user, critiqued_keyphrase, I_K, U_K, reg, k = 10):
    """
    P@k for affected elements
    Minimize w1*abs(post_rating - pre_rating) + w2(post_rating - pre_rating)
    Constraint: 0 < lambda < 1
    where w1/w2 = items in critiqued list / items in non-critiqued list
    Optimal Critiqued Vector  =  (1 - lambda) * initial + lambda * modified
    """
    affected_items = I_K.T[critiqued_keyphrase].nonzero()[1]
    unaffected_items = np.setdiff1d(range(num_items), affected_items)
    
    # Pre-calculation for LP 
    critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase)
    critiqued_matrix = reg.predict(critiqued_matrix_onehot)

    U_V_critique = critiqued_matrix
    U_V_initial = Y
    latent_diff = U_V_critique - U_V_initial
    
    # Setting up objective function
    def objective(lamb):
        "get precision @ k"
        # Getting prediction result
        modified_matrix_diff = (1-lamb)*Y + lamb*critiqued_matrix_diff 
        prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                 matrix_V=RQ,
                                 )
        prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                              train_vector=rtrain[user],
                                              remove_train=True)
        # Get how many affected items in topk
        hit = np.in1d(prediction_items[:k], affected_items)
        print (sum(hit))
        return sum(hit)
    x0 = np.array([0.5])
    bds = ((0,1),) # Constraint on lambda
    sol = minimize(objective, x0, bounds = bds)
    optimal_lamb = sol.x
    return optimal_lamb
def Object3(user, critiqued_keyphrase, I_K, U_K, reg, k = 10):
    """
    P@k for affected elements
    Minimize w1*abs(post_rating - pre_rating) + w2(post_rating - pre_rating)
    Constraint: 0 < lambda < 1
    where w1/w2 = items in critiqued list / items in non-critiqued list
    Optimal Critiqued Vector  =  (1 - lambda) * initial + lambda * modified
    """
    affected_items = I_K.T[critiqued_keyphrase].nonzero()[1]
    unaffected_items = np.setdiff1d(range(num_items), affected_items)
    
    # Pre-calculation for LP 
    critiqued_matrix_onehot = get_critiqued_UK(U_K,user,critiqued_keyphrase)
    critiqued_matrix = reg.predict(critiqued_matrix_onehot)

    U_V_critique = critiqued_matrix
    U_V_initial = Y
    latent_diff = U_V_critique - U_V_initial
    
    # Setting up objective function
    def objective(lamb):
        "get precision @ k"
        # Getting prediction result
        modified_matrix_diff = (1-lamb)*Y + lamb*critiqued_matrix_diff 
        prediction_scores_u = predict_scores(matrix_U=modified_matrix_diff[user], 
                                 matrix_V=RQ,
                                 )
        prediction_items = predict_vector(rating_vector=prediction_scores_u,
                                              train_vector=rtrain[user],
                                              remove_train=True)
        # Get how many affected items in topk
        hit = np.in1d(prediction_items[:k], affected_items)
        off_hit = np.in1d(prediction_items[:k], unaffected_items)
        print ('affected hit', sum(hit))
#         print ('initial hit', sum(hit))
        print ('unaffected hit', sum(off_hit))
        return sum(hit) - sum(off_hit)
    x0 = np.array([0.8])
    bds = ((0,1),) # Constraint on lambda
    sol = minimize(objective, x0, bounds = bds)
    optimal_lamb = sol.x
    return optimal_lamb
Object3(user, critiqued_keyphrase, I_K, U_K, reg, k = 100)
# PLREC
columns = ['user_id', 'target_item', 'item_name', 'iter', 'pre_rank', 
           'top_prediction_item_name',
           'post_rank_random_all',
           'post_rank_random_upper',
           'post_rank_pop_all',
           'post_rank_pop_upper',
           'post_rank_diff_all',
           'post_rank_diff_upper',
           'critiqued_keyphrase_random',
           'keyphrase_name_random',
           'critiqued_keyphrase_pop',
           'keyphrase_name_pop',
           'critiqued_keyphrase_diff',
           'keyphrase_name_diff',
           'num_existing_keyphrases',
           'pure_pruning_rank'] 
df = pd.DataFrame(columns=columns)
row = {}

#only_with_critiqued_keyphrase
for user in range(300,301):
    df = single_step_critiquing_plrec(user = user, 
                           keyphrase_length_threshold = 230, 
                           max_iteration_threshold = 10,
                           k = 50,
                           df = df,
                           row = row,
                           business_df = business_df,
                           keyphrases = keyphrases,
                           keyphrase_popularity = keyphrase_popularity,
                           keyphrase_selection_method = 'random',
                           recommend_type = 'all',
                           w1 = 10 * 1e-5, w2 = 10 * 1e-5
                           )
df.to_csv(single_step_with_avg_path+"test.csv")
df<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09125432357257957, 'fraction_numerical': 0.010171300715378062, 'mean_word_length': 2.2952954307166773, 'pattern_counts': {'":': 6, '<': 78, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 256, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '180373', 'license': 'MIT', 'n_tokens_mistral': 30277, 'n_tokens_neox': 28775, 'n_words': 5222, 'path': 'Critiques Draft/.ipynb_checkpoints/Single Step Critiquing Experiment-checkpoint.ipynb', 'repo_name': 'litosly/thesis', 'size': '97431'}