Domain: Business Operations & Analytics

Based on the provided data, answer the following questions:
1. By Brands, compute the difference between the highest average Discount for High Ratings Rating  >= 4.2 and the lowest average Discount for Low Ratings Rating < 4.2.
2. For phones with Memory>= 6 GB and Storage >= 128 GB, which Brand has the lowest ratio of average Selling Price to average Original Price? Return the ratio.
3. What is the average Discount Percentage for phones with Rating >= 3.5 in the bottom 10th percentile of Selling Price within that rating group? Consider linear interpolation for the 10th percentile threshold.
4. What is the average Original Price for phones with Memory in the top 25% (Q3 and above)?
5. What is the average Selling Price for phones with Memory greater than or equal to 8 GB?

Data Cleaning & Processing Rules
1. Make sure to load all provided data.
2. Remove exact duplicate rows.
3. Rename the column discount  percentage (with double spaces) to Discount Percentage.
4. Treat non-standard values like "Expandable Upto..." or missing values as NaN.
5. Remove rows where Memory or Storage is missing/NaN after parsing.
6. Remove rows where Selling Price > Original Price or where Discount < 0.
7. Impute missing Rating using the mean of the corresponding Brand.
8. Remove rows where Rating is still missing or where Rating > 5.
9. All final numeric answers must be explicitly rounded to 4 decimal places and formatted to show exactly 4 decimal digits.

Print the final answers as a Python list containing 5 string elements

Grading rubric:
criterion: Does the solution remove exact duplicate rows?
weight: 5
criterion: Does the solution parse Memory and Storage columns to numeric GB values?
weight: 2
criterion: Does the solution treat 1 TB as 1024 GB when parsing Memory and Storage?
weight: 2
criterion: Does the solution treat non-standard values like 'Expandable Upto...' or missing values as NaN?
weight: 2
criterion: Does the solution remove rows where Selling Price > Original Price or where Discount < 0?
weight: 5
criterion: Does the solution impute missing Rating values using the mean of the corresponding Brands?
weight: 5
criterion: Does the solution remove rows where Rating > 5?
weight: 5
criterion: Does the solution report the difference between the highest avg Discount (High Rating) and lowest avg Discount (Low Rating) as 9126.7917?
weight: 20
criterion: Does the solution report the lowest ratio of avg Selling Price to avg Original Price (for specified memory/storage) as 0.6772?
weight: 20
criterion: Does the solution report the average Discount Percentage for phones with Rating >= 3.5 in the bottom 10% of Selling Price as 3.9659?
weight: 20
criterion: Does the solution report the average Original Price for phones with top 25% Memory as 39073.0261?
weight: 20
criterion: Does the solution report the average Selling Price for phones with Memory >= 8 GB as 38452.7789?
weight: 20
criterion: Are the values inside the final output formatted with exactly 4 decimal places?
weight: 5
criterion: Does the solution print the final results as a single Python list?
weight: 5
criterion: Does the final list contain exactly 5 string elements?
weight: 5

Golden answer:
## Step-by-Step Reasoning

Step 1: Load and merge all data
The dataset is spread across three sheets (Part1, Part2, Part3). Each sheet is loaded into a separate DataFrame and all three are concatenated into a single DataFrame with `pd.concat`.

Step 2: Remove exact duplicate rows
`drop_duplicates()` is applied to the combined DataFrame so that no record is counted more than once in any computation.

Step 3: Rename the discount column
The column `discount  percentage` (containing two internal spaces) is renamed to `Discount Percentage` for consistent referencing throughout the analysis.

Step 4: Parse Memory and Storage to numeric GB
Both columns hold mixed-format strings ("6 GB", "128 GB", "1 TB"). A `parse_to_gb` function uses a regex to extract the numeric value and unit, then converts: TB -> multiply by 1024, MB -> divide by 1024, GB -> use as-is. Values whose string representation starts with "Expandable" and any unrecognised patterns are returned as NaN.

Step 5: Remove rows with missing Memory or Storage
After parsing, `dropna(subset=["Memory", "Storage"])` removes every row where either column could not be resolved to a valid numeric value.

Step 6: Remove rows with invalid prices or discounts
Rows where `Selling Price > Original Price` or `Discount < 0` are filtered out, as these represent impossible or erroneous entries.

Step 7: Impute missing Ratings
For each brand, the mean `Rating` is computed from all non-missing values in that brand group. Missing `Rating` entries are then filled with the corresponding brand mean via `groupby(...).transform("mean")`.

Step 8: Remove rows with invalid Ratings
After imputation, any row still missing a `Rating` (brands with no valid rating at all) and any row with `Rating > 5` are dropped.

Step 9: Compute Question 1
Phones are split into two groups: High Rating (`Rating >= 4.2`) and Low Rating (`Rating < 4.2`). For each group, the average `Discount` is computed per Brand. The answer is the maximum of the High-Rating brand averages minus the minimum of the Low-Rating brand averages.

max(avg High-Rating Discount per Brand) − min(avg Low-Rating Discount per Brand) = 9126.7917

Step 10: Compute Question 2
The dataset is filtered to phones with `Memory >= 6 GB` and `Storage >= 128 GB`. For every Brand in this subset, the ratio of average Selling Price to average Original Price is calculated. The answer is the minimum ratio across all qualifying brands.

min(avg Selling Price / avg Original Price) = 0.6772

Step 11: Compute Question 3
Phones with `Rating >= 3.5` are selected. Within this subset, the 10th percentile of `Selling Price` is computed using linear interpolation. Phones at or below that threshold form the "bottom 10th percentile" group. The answer is the mean `Discount Percentage` for that group.

mean(Discount Percentage for Rating >= 3.5 and Selling Price ≤ p10) = 3.9659

Step 12: Compute Question 4
The 75th percentile (Q3) of `Memory` is computed across the full cleaned dataset. Phones with `Memory >= Q3` form the top 25% group. The answer is the mean `Original Price` for this group.

mean(Original Price for Memory ≥ Q3) = 39073.0261

Step 13: Compute Question 5
All phones with `Memory >= 8 GB` are selected. The answer is the mean `Selling Price` for this group.

mean(Selling Price for Memory ≥ 8 GB) = 38452.7789


```python
import pandas as pd
import numpy as np
import re

file_path = "data.xlsx"

# Load dataset
df1 = pd.read_excel(file_path, sheet_name="Part1")
df2 = pd.read_excel(file_path, sheet_name="Part2")
df3 = pd.read_excel(file_path, sheet_name="Part3")

# Merge
df = pd.concat([df1, df2, df3], ignore_index=True)

# Remove duplicates and rename column
df = df.drop_duplicates()
df = df.rename(columns={"discount  percentage": "Discount Percentage"})


# Parse Memory and Storage to numeric GB
def parse_to_gb(val):
    if pd.isna(val):
        return np.nan
    val = str(val).strip()
    # Skip values that are explicitly about expandability (usually not the main storage)
    if val.lower().startswith("expandable"):
        return np.nan

    match = re.search(r"(\d+(?:\.\d+)?)\s*(TB|GB|MB)", val, re.IGNORECASE)

    if match:
        number = float(match.group(1))
        unit = match.group(2).upper()

        if unit == "TB":
            return number * 1024
        elif unit == "GB":
            return number
        elif unit == "MB":
            return number / 1024
    print(val)
    return np.nan


df["Memory"] = df["Memory"].apply(parse_to_gb)
df["Storage"] = df["Storage"].apply(parse_to_gb)

# Filter missing memory/storage
df = df.dropna(subset=["Memory", "Storage"])

# Filter invalid prices and discounts
df = df[~(df["Selling Price"] > df["Original Price"])]
df = df[~(df["Discount"] < 0)]

# Impute missing Rating with Brand mean
brand_mean = df.groupby("Brands")["Rating"].transform("mean")
df["Rating"] = df["Rating"].fillna(brand_mean)

# Filter invalid ratings
df = df.dropna(subset=["Rating"])
df = df[df["Rating"] <= 5]

# 1. Difference between highest avg Discount (High Rating) and lowest avg Discount (Low Rating)
high_rating = df[df["Rating"] >= 4.2]
low_rating = df[df["Rating"] < 4.2]
avg_high = high_rating.groupby("Brands")["Discount"].mean()
avg_low = low_rating.groupby("Brands")["Discount"].mean()
ans1 = avg_high.max() - avg_low.min()

# 2. Brand with lowest ratio of avg Selling Price to avg Original Price
filt = df[(df["Memory"] >= 6) & (df["Storage"] >= 128)]
ratio = (
    filt.groupby("Brands")["Selling Price"].mean()
    / filt.groupby("Brands")["Original Price"].mean()
)
ans2 = ratio.min()

# 3. Avg Discount Percentage for Rating >= 3.5 in bottom 10% of Selling Price
hr = df[df["Rating"] >= 3.5]
p10 = hr["Selling Price"].quantile(0.10, interpolation="linear")
bottom10 = hr[hr["Selling Price"] <= p10]
ans3 = bottom10["Discount Percentage"].mean()

# 4. Avg Original Price for Memory in top 25%
q75 = df["Memory"].quantile(0.75)
top25 = df[df["Memory"] >= q75]
ans4 = top25["Original Price"].mean()

# 5. Avg Selling Price for phones with Memory >= 8 GB
q5_target = df[df["Memory"] >= 8]
if not q5_target.empty:
    ans5 = q5_target["Selling Price"].mean()
else:
    ans5 = 0

print(
    [f"{ans1:.4f}", f"{ans2:.4f}", f"{ans3:.4f}", f"{ans4:.4f}", f"{ans5:.4f}"]
)

```

['9126.7917', '0.6772', '3.9659', '39073.0261', '38452.7789']