コード例 #1
0
ファイル: experiment.3.py プロジェクト: maoty2011/decisions
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 setUp(self):
        self.world = World(real_time_tick=0.200)

        person_0 = Person('20170101')
        person_1 = Person('20170202')
        person_2 = Person('20170707')

        offer_a = Offer(0)
        offer_b = Offer(1)

        self.delimiter = '|'
        deliveries_path = 'data/delivery'
        self.deliveries_file_name = 'data/test_deliveries.csv'
        self.deliveries = [(person_0.id, offer_a.id),
                           (person_1.id, offer_a.id),
                           (person_2.id, offer_b.id)]

        with open(self.deliveries_file_name, 'w') as deliveries_file:
            for delivery in self.deliveries:
                print >> deliveries_file, self.delimiter.join(
                    map(str, delivery))

        self.population = Population(
            self.world,
            people=(person_0, person_1, person_2),
            portfolio=(offer_a, offer_b),
            deliveries_path=deliveries_path,
            transcript_file_name='data/transcript.json')
コード例 #3
0
def create_portfolio():
    offer_a = Offer(0)
    offer_b = Offer(1)

    portfolio = (offer_a, offer_b)

    return portfolio
コード例 #4
0
ファイル: person.py プロジェクト: maoty2011/decisions
    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)
コード例 #5
0
ファイル: person.py プロジェクト: maoty2011/decisions
    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
コード例 #6
0
ファイル: person.py プロジェクト: maoty2011/decisions
    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])
コード例 #7
0
    def from_dict(population_dict):

        population = Population(
            World.from_dict(population_dict.get('world')),
            people=[
                Person.from_dict(person_dict)
                for person_dict in population_dict.get('people')
            ],
            portfolio=[
                Offer.from_dict(offer_dict)
                for offer_dict in population_dict.get('portfolio')
            ],
            deliveries_path=population_dict.get('deliveries_path'),
            transcript_file_name=population_dict.get('transcript_file_name'))

        return population
コード例 #8
0
    def read_offer_portfolio(self, portfolio_file_name):
        """Read in an offer portfolio from a file.

        An offer portfolio file contains one json object per line.
        Each json object represents a single offer.
        """

        with open(portfolio_file_name, 'r') as portfolio_file:
            for line in portfolio_file:
                offer_json = line.strip()

                # skip blank lines
                if offer_json != '':
                    offer = Offer.from_json(offer_json)
                    offer_id = offer['id']
                    if offer_id not in self.offer_portfolio:
                        self.offer_portfolio[id] = offer
                    else:
                        raise ValueError(
                            'ERROR - Offer id {} is not unique. It is already present in the offer portfolio.'
                            .format(offer_id))
コード例 #9
0
ファイル: person.py プロジェクト: maoty2011/decisions
    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')
コード例 #10
0
ファイル: person.py プロジェクト: maoty2011/decisions
    def make_purchase(self, world):
        """Person decides whether to make a purcahse or not and the size of the purchase. Includes outliers, e.g., due
        to large group orders vs. individual orders.

        Depends on time of day, segment, income, how long since last purchase, offers
        """

        # logging.debug('Made purchase decision at time t = {}'.format(world.world_time))

        # How long since last transaction
        if self.last_transaction is not None:
            time_since_last_transaction = world.world_time - self.last_transaction.timestamp
        else:
            time_since_last_transaction = 0

        # How long since last viewed offer
        offer = self.last_viewed_offer
        if offer is not None:
            time_since_last_viewed_offer = world.world_time - offer.timestamp
            last_viewed_offer_duration = offer.valid_until - offer.timestamp
            viewed_active_offer = 1 if offer.is_active(world.world_time) else 0
            offer_channel_weights = offer.channel.weights
        else:
            # never viewed an offer, so as if it's been forever
            time_since_last_viewed_offer = Constants.END_OF_TIME - Constants.BEGINNING_OF_TIME
            last_viewed_offer_duration = 0
            viewed_active_offer = 0
            offer_channel_weights = Offer(
                Constants.BEGINNING_OF_TIME).channel.zeros

        # as time since last offer increases, the effect should go to zero: x_max = T, f_of_x_max = 0
        # the offer view is most powerful immediately: x_min = 0, f_of_x_min = 1
        # therefore we have a function that should decrease from 1 to 0 as x increases from 0 to T
        # also, the sensitivity should be positive (the negative effect lies in the state variable)
        # T is the time at which the viewed offer no longer has an effect
        # let's make this 3 days after the offer expires = offer_length + 24/float(world.world_time_tick) * 3
        last_viewed_offer_strength = self.bounded_response(
            time_since_last_viewed_offer,
            min_x=0,
            max_x=last_viewed_offer_duration +
            24 / float(world.world_time_tick) * 3,
            f_of_min_x=1.0,
            f_of_max_x=0.0)

        beta = self.make_purchase_sensitivity.weights
        x = numpy.array((1, time_since_last_transaction,
                         last_viewed_offer_strength, viewed_active_offer))
        p = 1.0 / (1.0 + numpy.exp(-numpy.dot(beta, x)))

        # logging.debug('        beta = {}'.format(beta))
        # logging.debug('           x = {}'.format(x))
        # logging.debug('      beta*x = {}'.format(beta*x))
        # logging.debug('dot(beta, x) = {}'.format(numpy.dot(beta, x)))
        # logging.debug('           p = {}'.format(p))

        # flip a coin to decide if a purchase was made
        made_purchase = True if numpy.random.random() < p else False

        if made_purchase:
            # logging.debug('Made purchase')
            # Determine if this is an outlier order or regular order
            if numpy.random.random() < self.outlier_frequency:
                purchase_amount = self.outlier_purchase_amount(world)
            else:
                purchase_amount = self.purchase_amount(world)

            transaction = Transaction(world.world_time, amount=purchase_amount)
            self.history.append(transaction)
            self.last_transaction = transaction
        else:
            transaction = None

        return transaction