|
| 1 | +import numpy as np |
| 2 | +import pandas as pd |
| 3 | + |
| 4 | +def f_1718(products, n_samples=100, sales_lower=50, sales_upper=200, profit_margin_min=0.1, profit_margin_max=0.5, random_seed=42): |
| 5 | + """ |
| 6 | + Generate a sales report with randomly simulated sales and profit data for a given list of products. |
| 7 | + The data is aggregated by product and sorted by total profit in descending order. |
| 8 | + |
| 9 | + Parameters: |
| 10 | + - products (list of str): List of product names. |
| 11 | + - n_samples (int): The number of data points to generate for the report. Default is 100. |
| 12 | + - sales_lower (int): The minimum sales value for the random generation. Default is 50. |
| 13 | + - sales_upper (int): The maximum sales value for the random generation. Default is 200. |
| 14 | + - profit_margin_min (float): The minimum profit margin as a fraction of sales. Default is 0.1. |
| 15 | + - profit_margin_max (float): The maximum profit margin as a fraction of sales. Default is 0.5. |
| 16 | + - random_seed (int): Seed for the random number generator to ensure reproducibility. Default is 42. |
| 17 | +
|
| 18 | + Returns: |
| 19 | + pd.DataFrame: A DataFrame containing aggregated sales and profit data for each product, sorted by profit. |
| 20 | +
|
| 21 | + Raises: |
| 22 | + ValueError: If n_samples is not a positive integer, or if sales_lower is greater than sales_upper. |
| 23 | + TypeError: If products is not a list of strings, or if sales_lower, sales_upper, profit_margin_min, or profit_margin_max are not numeric. |
| 24 | +
|
| 25 | + Requirements: |
| 26 | + - numpy |
| 27 | + - pandas |
| 28 | +
|
| 29 | + Example: |
| 30 | + >>> products = ["iPhone", "iPad", "Macbook", "Airpods", "Apple Watch"] |
| 31 | + >>> report = f_1718(products, n_samples=50, sales_lower=100, sales_upper=150, profit_margin_min=0.2, profit_margin_max=0.4, random_seed=42) |
| 32 | + >>> print(report) |
| 33 | + Product Sales Profit |
| 34 | + 2 Macbook 1561 444.826709 |
| 35 | + 3 iPad 1383 401.925334 |
| 36 | + 0 Airpods 1297 381.482713 |
| 37 | + 1 Apple Watch 1123 308.078536 |
| 38 | + 4 iPhone 921 294.013887 |
| 39 | + """ |
| 40 | + np.random.seed(random_seed) |
| 41 | + |
| 42 | + if not products: |
| 43 | + return pd.DataFrame(columns=["Product", "Sales", "Profit"]) |
| 44 | + |
| 45 | + if not isinstance(products, list) or not all(isinstance(product, str) for product in products): |
| 46 | + raise TypeError("products must be a list of strings.") |
| 47 | + if not isinstance(n_samples, int) or n_samples <= 0: |
| 48 | + raise ValueError("n_samples must be a positive integer.") |
| 49 | + if not (isinstance(sales_lower, int) and isinstance(sales_upper, int)) or sales_lower >= sales_upper: |
| 50 | + raise ValueError("sales_lower must be less than sales_upper and both must be integers.") |
| 51 | + if not all(isinstance(x, (int, float)) for x in [profit_margin_min, profit_margin_max]) or profit_margin_min >= profit_margin_max: |
| 52 | + raise ValueError("profit_margin_min must be less than profit_margin_max and both must be numeric.") |
| 53 | + |
| 54 | + data = [] |
| 55 | + for _ in range(n_samples): |
| 56 | + product = np.random.choice(products) |
| 57 | + sales = np.random.randint(sales_lower, sales_upper + 1) |
| 58 | + profit = sales * np.random.uniform(profit_margin_min, profit_margin_max) |
| 59 | + data.append([product, sales, profit]) |
| 60 | + |
| 61 | + df = pd.DataFrame(data, columns=["Product", "Sales", "Profit"]) |
| 62 | + df = df.groupby("Product", as_index=False).sum() |
| 63 | + df.sort_values("Profit", ascending=False, inplace=True) |
| 64 | + |
| 65 | + return df |
| 66 | + |
| 67 | +import pandas as pd |
| 68 | +import unittest |
| 69 | + |
| 70 | +class TestCases(unittest.TestCase): |
| 71 | + |
| 72 | + def test_random_reproducibility(self): |
| 73 | + report1 = f_1718(["iPhone", "iPad"], n_samples=50, sales_lower=50, sales_upper=200, profit_margin_min=0.1, profit_margin_max=0.5, random_seed=42) |
| 74 | + report2 = f_1718(["iPhone", "iPad"], n_samples=50, sales_lower=50, sales_upper=200, profit_margin_min=0.1, profit_margin_max=0.5, random_seed=42) |
| 75 | + pd.testing.assert_frame_equal(report1, report2) |
| 76 | + |
| 77 | + def test_number_of_rows(self): |
| 78 | + report = f_1718(["iPhone", "iPad"], n_samples=50, sales_lower=50, sales_upper=200) |
| 79 | + self.assertEqual(len(report), len(set(["iPhone", "iPad"]))) |
| 80 | + |
| 81 | + def test_sorting_by_profit(self): |
| 82 | + report = f_1718(["iPhone", "iPad"], sales_lower=50, sales_upper=200) |
| 83 | + self.assertTrue(report["Profit"].is_monotonic_decreasing) |
| 84 | + |
| 85 | + def test_custom_parameters(self): |
| 86 | + report = f_1718(["iPhone", "iPad", "Macbook", "Airpods", "Apple Watch"], n_samples=50, sales_lower=100, sales_upper=150, profit_margin_min=0.2, profit_margin_max=0.4, random_seed=42) |
| 87 | + # This test needs to be adjusted based on the expected outcome of the custom parameters. |
| 88 | + # Specific checks on DataFrame contents should account for the randomness and reproducibility aspects. |
| 89 | + self.assertTrue(len(report) > 0, "The report should contain aggregated sales and profit data.") |
| 90 | + |
| 91 | + def test_new_custom_parameters(self): |
| 92 | + report1 = f_1718(["iPhone", "iPad", "Macbook", "Airpods", "Apple Watch"], n_samples=50, sales_lower=100, sales_upper=150, profit_margin_min=0.2, profit_margin_max=0.4, random_seed=42) |
| 93 | + df_list = report1.apply(lambda row: ','.join(row.values.astype(str)), axis=1).tolist() |
| 94 | + expect = ['Macbook,1561,444.82670855378143', 'iPad,1383,401.9253335536443', 'Airpods,1297,381.4827132170069', 'Apple Watch,1123,308.07853599252707', 'iPhone,921,294.0138866107959'] |
| 95 | + |
| 96 | + self.assertEqual(df_list, expect, "DataFrame contents should match the expected output") |
| 97 | + |
| 98 | + def test_sales_bounds_validation(self): |
| 99 | + """Test that an error is raised if sales_lower is greater than sales_upper.""" |
| 100 | + with self.assertRaises(ValueError): |
| 101 | + f_1718(["Product1"], sales_lower=250, sales_upper=100) |
| 102 | + |
| 103 | + def test_profit_margin_validation(self): |
| 104 | + """Test that an error is raised if profit_margin_min is greater than or equal to profit_margin_max.""" |
| 105 | + with self.assertRaises(ValueError): |
| 106 | + f_1718(["Product1"], profit_margin_min=0.6, profit_margin_max=0.5) |
| 107 | + |
| 108 | + def test_product_list_validation(self): |
| 109 | + """Test that an error is raised if the products list is not a list of strings.""" |
| 110 | + with self.assertRaises(TypeError): |
| 111 | + f_1718([123, 456], n_samples=10) |
| 112 | + |
| 113 | + def test_n_samples_validation(self): |
| 114 | + """Test that an error is raised if n_samples is not a positive integer.""" |
| 115 | + with self.assertRaises(ValueError): |
| 116 | + f_1718(["Product1"], n_samples=-10) |
| 117 | + |
| 118 | + def test_empty_product_list(self): |
| 119 | + """Test that the function can handle an empty product list.""" |
| 120 | + report = f_1718([], n_samples=10) |
| 121 | + self.assertTrue(report.empty, "The report should be empty if no products are provided.") |
| 122 | + |
| 123 | + def test_zero_samples(self): |
| 124 | + """Test handling of zero samples.""" |
| 125 | + with self.assertRaises(ValueError): |
| 126 | + f_1718(["Product1"], n_samples=-10) |
| 127 | + |
| 128 | + def test_single_product_reproducibility(self): |
| 129 | + """Test that the function generates consistent results for a single product across multiple runs.""" |
| 130 | + report1 = f_1718(["Product1"], n_samples=10, random_seed=42) |
| 131 | + report2 = f_1718(["Product1"], n_samples=10, random_seed=42) |
| 132 | + pd.testing.assert_frame_equal(report1, report2) |
| 133 | + |
| 134 | + |
| 135 | +def run_tests(): |
| 136 | + """Run all tests for this function.""" |
| 137 | + loader = unittest.TestLoader() |
| 138 | + suite = loader.loadTestsFromTestCase(TestCases) |
| 139 | + runner = unittest.TextTestRunner() |
| 140 | + runner.run(suite) |
| 141 | + |
| 142 | +if __name__ == "__main__": |
| 143 | + import doctest |
| 144 | + doctest.testmod() |
| 145 | + run_tests() |
0 commit comments