Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.59% covered (success)
94.59%
35 / 37
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Metrics
94.59% covered (success)
94.59%
35 / 37
77.78% covered (warning)
77.78%
7 / 9
14.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 getCustomerRetention
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCoefficientOfRetention
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 predictCustomerRetention
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 customerActiveProbability
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBerrysCustomerProfits
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 calculateMailingSuccessEstimation
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 migrationModel
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 migrationModelPurchaseProbability
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 createCustomerPurchaseProbabilityMatrix
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Business\Marketing
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Business\Marketing;
16
17use phpOMS\Math\Matrix\IdentityMatrix;
18use phpOMS\Math\Matrix\Matrix;
19use phpOMS\Math\Matrix\Vector;
20
21/**
22 * Marketing Metrics
23 *
24 * This class provided basic marketing metric calculations
25 *
26 * @package phpOMS\Business\Marketing
27 * @license OMS License 2.0
28 * @link    https://jingga.app
29 * @since   1.0.0
30 */
31final class Metrics
32{
33    /**
34     * Constructor
35     *
36     * @since 1.0.0
37     * @codeCoverageIgnore
38     */
39    private function __construct()
40    {
41    }
42
43    /**
44     * Calculate customer retention
45     *
46     * @latex  r = \frac{ce - cn}{cs}
47     *
48     * @param int $ce Customer at the end of the period
49     * @param int $cn New customers during period
50     * @param int $cs Customers at the start of the period
51     *
52     * @return float Returns the customer retention
53     *
54     * @since 1.0.0
55     */
56    public static function getCustomerRetention(int $ce, int $cn, int $cs) : float
57    {
58        return ($ce - $cn) / $cs;
59    }
60
61    /**
62     * Calcualte the coefficient of retention
63     *
64     * @param float $retentionRate Observed retention rate (optionally use the average)
65     * @param float $rc            Retention rate ceiling
66     * @param int   $t             Period
67     *
68     * @return float
69     *
70     * @since 1.0.0
71     */
72    public static function getCoefficientOfRetention(float $retentionRate, float $rc, int $t) : float
73    {
74        return 1 / $t * \log($rc - $retentionRate);
75    }
76
77    /**
78     * Predict the retention rate for period t
79     *
80     * @param float $rc Retention rate ceiling
81     * @param float $r  Coefficient of retention
82     * @param int   $t  Period t
83     *
84     * @return float
85     *
86     * @since 1.0.0
87     */
88    public static function predictCustomerRetention(float $rc, float $r, int $t) : float
89    {
90        return $rc * (1 - \exp(-$r * $t));
91    }
92
93    /**
94     * Calculate the probability of a customer being active
95     *
96     * @param int $purchases    Number of purchases during the periods
97     * @param int $periods      Number of periods (e.g. number of months)
98     * @param int $lastPurchase In which period was the last purchase (lastPurchase = periods: means customer purchased in this period)
99     *
100     * @return float
101     *
102     * @since 1.0.0
103     */
104    public static function customerActiveProbability(int $purchases, int $periods, int $lastPurchase) : float
105    {
106        return \pow($lastPurchase / $periods, $purchases);
107    }
108
109    /**
110     * Calculate the customer profits
111     *
112     * @param int   $customers      Amount of customers acquired
113     * @param float $acquistionCost Acquisition cost per customer
114     * @param float $revenue        Revenues per period per customer
115     * @param float $cogs           COGS per period per customer
116     * @param float $marketingCosts Ongoing marketing costs per period per customer
117     * @param float $discountRate   Discount rate
118     * @param float $retentionRate  Retention rate (how many customers remain)
119     *
120     * @return float
121     *
122     * @since 1.0.0
123     */
124    public static function getBerrysCustomerProfits(
125        int $customers,
126        float $acquistionCost,
127        float $revenue,
128        float $cogs,
129        float $marketingCosts,
130        float $discountRate,
131        float $retentionRate
132    ) : float
133    {
134        return $customers * ($revenue - $cogs) * ((1 + $discountRate) / (1 + $discountRate - $retentionRate))
135            - $customers * $marketingCosts * ((1 + $discountRate) / (1 + $discountRate - $retentionRate))
136            - $customers * $acquistionCost;
137    }
138
139    /**
140     * Calculate the profitability of customers based on their purchase behaviour
141     *
142     * The basis for the calculation is the migration model using a markov chain
143     *
144     * @param float $discountRate        Discount rate
145     * @param array $purchaseProbability Purchase probabilities for different periods
146     * @param array $payoffs             Payoff vector (first element = payoff - cost, other elements = -cost, last element = 0)
147     *
148     * @return Matrix A vector which shows in row i the return of the customer if he didn't buy i - 1 times before
149     *                (=recency of the customer = how many periods has it been since he bought the last time)
150     *
151     * @since 1.0.0
152     */
153    public static function calculateMailingSuccessEstimation(float $discountRate, array $purchaseProbability, array $payoffs) : Matrix
154    {
155        $count  = \count($purchaseProbability);
156        $profit = new Vector($count, 1);
157        $G      = Vector::fromArray($payoffs);
158
159        $P    = self::createCustomerPurchaseProbabilityMatrix($purchaseProbability);
160        $newP = new IdentityMatrix($count);
161
162        // $i = 0;
163        $profit = $profit->add($G);
164
165        for ($i = 1; $i < $count + 1; ++$i) {
166            $newP   = $newP->mult($P);
167            $profit = $profit->add($newP->mult($G)->mult(1 / \pow(1 + $discountRate, $i)));
168        }
169
170        return $profit;
171    }
172
173    /**
174     * Calculate V of the migration model
175     *
176     * Pfeifer and Carraway 2000
177     *
178     * @param float $discountRate        Discount rate
179     * @param array $purchaseProbability Purchase probabilities for different periods
180     * @param array $payoffs             Payoff vector (first element = payoff - cost, other elements = -cost, last element = 0)
181     *
182     * @return Matrix [0][0] returns the LTV
183     *
184     * @since 1.0.0
185     */
186    public static function migrationModel(float $discountRate, array $purchaseProbability, array $payoffs) : Matrix
187    {
188        $P = self::createCustomerPurchaseProbabilityMatrix($purchaseProbability);
189        $I = new IdentityMatrix(\count($purchaseProbability));
190
191        return $I->sub(
192                $P->mult(1 / (1 + $discountRate))
193            )->inverse()
194            ->mult(Vector::fromArray($payoffs));
195    }
196
197    /**
198     * Calculate the purchase probability of the different purchase states.
199     *
200     * Pfeifer and Carraway 2000
201     *
202     * A customer can either buy in a certain period or not.
203     * Depending on the result he either moves on to the next state (not buying) or returns to the first state (buying).
204     *
205     * @param int   $period              Period to evaluate (t)
206     * @param array $purchaseProbability Purchase probabilities
207     *
208     * @return Matrix [
209     *                [0][0] = probability of buying in period t if customer bought in t = 1
210     *                ...
211     *                ]
212     */
213    public static function migrationModelPurchaseProbability(int $period, array $purchaseProbability) : Matrix
214    {
215        $matrix    = self::createCustomerPurchaseProbabilityMatrix($purchaseProbability);
216        $newMatrix = clone $matrix;
217
218        for ($i = 0; $i < $period - 1; ++$i) {
219            $newMatrix = $newMatrix->mult($matrix);
220        }
221
222        return $newMatrix;
223    }
224
225    /**
226     * Create a matrix which contains the probabilities a customer will buy in period t
227     *
228     * @param array $purchaseProbability Purchase probabilities for the different periods
229     *
230     * @latex \begin{bmatrix}
231     *      p_1 & 1 - p_1 & 0 \\
232     *      p_2 & 0 & 1 - p_2 \\
233     *      p_3 & 0 & 1 - p_3 \\
234     * \end{bmatrix}
235     *
236     * @return Matrix [
237     *                p1, 1-p1, 0,
238     *                p2, 0,    1-p2,
239     *                p3, 0,    1-p3,
240     *                ] where pi = Probability that customer buys in period i / moves from one state to the next state
241     *
242     * @since 1.0.0
243     */
244    private static function createCustomerPurchaseProbabilityMatrix(array $purchaseProbability) : Matrix
245    {
246        $matrix = [];
247
248        $count = \count($purchaseProbability);
249        for ($i = 0; $i < $count; ++$i) {
250            $matrix[$i]    = \array_fill(0, $count, 0);
251            $matrix[$i][0] = $purchaseProbability[$i];
252
253            $matrix[$i][
254                $i === $count - 1 ? $i : $i + 1
255            ] = 1 - $purchaseProbability[$i];
256        }
257
258        return Matrix::fromArray($matrix);
259    }
260}