示例#1
0
def create_portfolio():
    offer_channel = Categorical(('web', 'email', 'mobile', 'social'),
                                (1, 1, 1, 0))
    offer_type = Categorical(('bogo', 'discount', 'informational'), (1, 0, 0))
    discount_a = Offer(0,
                       valid_from=0,
                       valid_until=4 * 7 * 24,
                       difficulty=5,
                       reward=5,
                       channel=offer_channel,
                       offer_type=offer_type)

    offer_channel = Categorical(('web', 'email', 'mobile', 'social'),
                                (1, 1, 1, 1))
    offer_type = Categorical(('bogo', 'discount', 'informational'), (0, 1, 0))
    discount_b = Offer(0,
                       valid_from=0,
                       valid_until=4 * 7 * 24,
                       difficulty=5,
                       reward=2,
                       channel=offer_channel,
                       offer_type=offer_type)

    portfolio = (discount_a, discount_b)

    return portfolio
示例#2
0
    def test_view_offer(self):
        person_view_offer_sensitivity = Categorical(
            ['background', 'offer_age', 'web', 'email', 'mobile', 'social'],
            [0, -1, 1, 1, 1, 1])
        offer_channel = Categorical(('web', 'email', 'mobile', 'social'),
                                    (1, 1, 1, 1))
        offer_type = Categorical(('bogo', 'discount', 'informational'),
                                 (0, 1, 0))

        discount = Offer(0,
                         valid_from=10,
                         valid_until=20,
                         difficulty=10,
                         reward=2,
                         channel=offer_channel,
                         offer_type=offer_type)
        person = Person(became_member_on='20170716',
                        view_offer_sensitivity=person_view_offer_sensitivity)
        person.last_unviewed_offer = discount

        world = copy.deepcopy(self.world)
        world.world_time = 0

        person.view_offer(world)

        self.assertTrue(True)
示例#3
0
    def from_dict(person_dict):

        history = list()
        for event_dict in person_dict.get('history'):
            event_type = event_dict.get('type')
            if event_type == 'event':
                event = Event.from_dict(event_dict)
            elif event_type == 'offer':
                event = Offer.from_dict(event_dict)
            elif event_type == 'transaction':
                event = Transaction.from_dict(event_dict)
            else:
                raise ValueError(
                    'ERROR - Event type not recognized ({}).'.format(
                        event_type))

            history.append(event)

        person = Person(                            person_dict.get('became_member_on'),                                        \
                                                 id=person_dict.get('id'),                                                      \
                                                dob=person_dict.get('dob'),                                                     \
                                             gender=person_dict.get('gender'),                                                  \
                                             income=person_dict.get('income'),                                                  \
                                              taste=Categorical.from_dict(person_dict.get('taste')),                            \
                                  marketing_segment=Categorical.from_dict(person_dict.get('marketing_segment')),                \
                                   last_transaction=person_dict.get('last_transaction'),                                        \
                                last_unviewed_offer=person_dict.get('last_unviewed_offer'),                                     \
                                  last_viewed_offer=person_dict.get('last_viewed_offer'),                                       \
                                            history=history,                                                                    \
                             view_offer_sensitivity=Categorical.from_dict(person_dict.get('view_offer_sensitivity')),           \
                          make_purchase_sensitivity=Categorical.from_dict(person_dict.get('make_purchase_sensitivity')),        \
                        purchase_amount_sensitivity=Categorical.from_dict(person_dict.get('purchase_amount_sensitivity')))

        return person
示例#4
0
    def setUp(self):
        self.offer = Offer(10,
                           channel=Categorical(
                               ('web', 'email', 'mobile', 'social'),
                               (0, 1, 1, 1)),
                           offer_type=Categorical(
                               ('bogo', 'discount', 'informational'),
                               (0, 0, 1)))
        self.transaction = Transaction(20, amount=1.00)
        self.world = World()

        self.person = Person(became_member_on='20170101',
                             history=[self.offer, self.transaction])
示例#5
0
def create_people_0(n):
    profile_optout_rate = 0.1
    min_tenure = 0
    max_tenure = 365
    mean_age = 365 * 30
    std_age = 365 * 2
    gender_rates = [0.49, 0.49, 0.02]
    min_income = 50000
    max_income = 75000
    beta = 1.0 / 0.0004
    g = lambda x: 1.0 / (1.0 + numpy.exp(-x))
    g_inv = lambda y: numpy.log(y / (1.0 - y))

    people = list()
    for i in range(n):
        became_member_on = (now - datetime.timedelta(
            days=numpy.random.choice(range(min_tenure, max_tenure)))
                            ).strftime(dt_fmt)

        if numpy.random.random() < 1.0 - profile_optout_rate:
            # must be at least 18 to join
            dob = (now - datetime.timedelta(days=int(
                max(365.25 * 18, (numpy.random.normal(mean_age, std_age)))))
                   ).strftime(dt_fmt)
            # three values + missing
            gender = numpy.random.choice(['M', 'F', 'O'], p=gender_rates)
            income = None
            for i in range(25):
                x = max(25000, numpy.random.exponential(beta))
                if min_income <= x <= max_income:
                    income = x
                    break
        else:
            dob = None
            gender = None
            income = None

        person_view_offer_sensitivity = Categorical(
            ['background', 'offer_age', 'web', 'email', 'mobile', 'social'], [
                g_inv(0.20) - 4, -abs(g_inv(0.01) / float(1 * 24 * 30)), 0, 1,
                1, 2
            ])
        person_make_purchase_sensitivity = Categorical([
            'background', 'time_since_last_transaction',
            'last_viewed_offer_strength', 'viewed_active_offer'
        ], [g_inv(1.0 / 24.0),
            abs(g_inv(0.10) / float(1 * 24 * 30)), 1, 1])
        person_purchase_amount_sensitivity = Categorical([
            'background', 'income_adjusted_purchase_sensitivity', 'front page',
            'local', 'entertainment', 'sports', 'opinion', 'comics', 'sweet',
            'sour', 'salty', 'bitter', 'umami'
        ], [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

        people.append(
            Person(
                became_member_on,
                dob=dob,
                gender=gender,
                income=income,
                view_offer_sensitivity=person_view_offer_sensitivity,
                make_purchase_sensitivity=person_make_purchase_sensitivity,
                purchase_amount_sensitivity=person_purchase_amount_sensitivity)
        )

    return people
示例#6
0
    def __init__(self, became_member_on, **kwargs):
        """Initialize Person.

        became_member_on: date (cannot be missing - assigned when an individual becomes a member)

        kwargs:
            dob: date (default = 19010101 - sound silly? it happens in the real world and skews age distributions)
            gender: M, F, O (O = other, e.g., decline to state, does not identify, etc.)
            income: positive int, None
            taste: categorical('sweet', 'sour', 'salty', 'bitter', 'umami')
            marketing_segment: categorical('front page', 'local', 'entertainment', 'sports', 'opinion', 'comics')
            offer_sensitivity: categorical
            make_purchase_sensitivity: ???
        """
        valid_kwargs = {
            'id', 'dob', 'gender', 'income', 'taste', 'marketing_segment',
            'last_transaction', 'last_unviewed_offer', 'last_viewed_offer',
            'history', 'view_offer_sensitivity', 'make_purchase_sensitivity',
            'purchase_amount_sensitivity'
        }
        kwargs_name_set = set(kwargs.keys())
        assert kwargs_name_set.issubset(
            valid_kwargs), 'ERROR - Invalid kwargs: {}'.format(
                kwargs_name_set.difference(valid_kwargs))

        ######################
        # Intrinsic Attributes
        ######################

        self.id = kwargs.get('id') if kwargs.get(
            'id') is not None else uuid.uuid4().hex
        self.dt_fmt = '%Y%m%d'
        try:
            datetime.datetime.strptime(kwargs.get('dob'), self.dt_fmt)
            self.dob = kwargs.get('dob')
        except:
            self.dob = '19010101'
        self.gender = kwargs.get('gender')
        try:
            datetime.datetime.strptime(became_member_on, self.dt_fmt)
            self.became_member_on = became_member_on
        except:
            raise ValueError(
                'ERROR - became_member_on has invalid format (should be: {}). became_member_on={}'
                .format(self.dt_fmt, became_member_on))
        self.income = kwargs.get('income')

        default_taste = Categorical(self.taste_names)
        kwargs_taste = kwargs.get('taste')
        if kwargs_taste is not None:
            assert default_taste.compare_names(
                kwargs_taste
            ), 'ERROR - keyword argument taste must have names = {}'.format(
                default_taste.names)
            self.taste = kwargs_taste
            self.taste.set_order(default_taste.names)
        else:
            self.taste = default_taste

        default_marketing_segment = Categorical(self.marketing_segment_names)
        kwargs_marketing_segment = kwargs.get('marketing_segment')
        if kwargs_marketing_segment is not None:
            assert default_marketing_segment.compare_names(
                kwargs_marketing_segment
            ), 'ERROR - keyword argument marketing_segment must have names = {}'.format(
                default_marketing_segment.names)
            self.marketing_segment = kwargs_marketing_segment
            self.marketing_segment.set_order(kwargs_marketing_segment.names)
        else:
            self.marketing_segment = default_marketing_segment

        ######################
        # Extrinsic Attributes
        ######################

        # only allow one offer to be active at a time, and only count as active if it's been viewed (no accidental
        # winners) - if offers have overlapping validity periods, then last received is the winner

        # Person has a short memory. Only the most recently received offer can be viewed (a newly received offer will
        # supplant it), and only the most recently viewed offer can influence Person's behavior (view another and Person
        # forgets). However, whether an offer is viewed or not, the user can still accidentally win by making a
        # sufficient purchase. If two offers are open simultanrously, then Person can get double credit (win both) with
        # a single purchase.

        self.last_transaction = kwargs.get('last_transaction')
        self.last_unviewed_offer = kwargs.get('last_unviewed_offer')
        self.last_viewed_offer = kwargs.get('last_viewed_offer')

        #########
        # History
        #########

        # A list of events. Since events have a timestamp, this is equivalent to a time series.
        kwargs_history = kwargs.get('history', list())
        # note that all(list()) returns True
        assert all(map(lambda e: isinstance(e, Event), kwargs_history)
                   ), 'ERROR - Not all items in history are of type Event.'
        self.history = kwargs_history

        ###################
        # Sensitivity
        ###################

        # view_offer_sensitivity
        view_offer_sensitivity_names = numpy.concatenate((numpy.array(
            ('background', 'offer_age')), Offer(0).channel.names))
        default_view_offer_sensitivity = Categorical(
            view_offer_sensitivity_names)
        default_view_offer_sensitivity.set('offer_age', -1)
        kwargs_view_offer_sensitivity = kwargs.get('view_offer_sensitivity',
                                                   None)
        if kwargs_view_offer_sensitivity is not None:
            assert default_view_offer_sensitivity.compare_names(
                kwargs_view_offer_sensitivity
            ), 'ERROR - keyword argument view_offer_sensitivity must have names = {}'.format(
                default_view_offer_sensitivity.names)
            self.view_offer_sensitivity = copy.deepcopy(
                kwargs_view_offer_sensitivity)
            self.view_offer_sensitivity.set_order(
                default_view_offer_sensitivity.names)
        else:
            self.view_offer_sensitivity = default_view_offer_sensitivity

        # make_purchase_sensitivity
        make_purchase_sensitivity_names = numpy.array(
            ('background', 'time_since_last_transaction',
             'last_viewed_offer_strength', 'viewed_active_offer'))
        default_make_purchase_sensitivity = Categorical(
            make_purchase_sensitivity_names)
        default_make_purchase_sensitivity.set('time_since_last_viewed_offer',
                                              -1)
        kwargs_make_purchase_sensitivity = kwargs.get(
            'make_purchase_sensitivity', None)
        if kwargs_make_purchase_sensitivity is not None:
            assert default_make_purchase_sensitivity.compare_names(
                kwargs_make_purchase_sensitivity
            ), 'ERROR - keyword argument make_purchase_sensitivity must have names = {}'.format(
                default_make_purchase_sensitivity.names)
            self.make_purchase_sensitivity = copy.deepcopy(
                kwargs_make_purchase_sensitivity)
            self.make_purchase_sensitivity.set_order(
                default_make_purchase_sensitivity.names)
        else:
            self.make_purchase_sensitivity = default_make_purchase_sensitivity

        # purchase_amount_sensitivity
        purchase_amount_sensitivity_names = numpy.concatenate(
            (numpy.array(
                ('background', 'income_adjusted_purchase_sensitivity')),
             self.marketing_segment_names, self.taste_names))
        default_purchase_amount_sensitivity = Categorical(
            purchase_amount_sensitivity_names)
        kwargs_purchase_amount_sensitivity = kwargs.get(
            'purchase_amount_sensitivity', None)
        if kwargs_purchase_amount_sensitivity is not None:
            assert default_purchase_amount_sensitivity.compare_names(
                kwargs_purchase_amount_sensitivity
            ), 'ERROR - keyword argument purchase_amount_sensitivity must have names = {}'.format(
                default_purchase_amount_sensitivity.names)
            default_purchase_amount_sensitivity.set(
                'income_adjusted_purchase_sensitivity', 1)
            self.purchase_amount_sensitivity = copy.deepcopy(
                kwargs_purchase_amount_sensitivity)
            self.purchase_amount_sensitivity.set_order(
                default_purchase_amount_sensitivity.names)
        else:
            self.purchase_amount_sensitivity = default_purchase_amount_sensitivity

        logging.info('Person initialized')