-
Notifications
You must be signed in to change notification settings - Fork 8
Mohammed A #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Mohammed A #5
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,23 +6,58 @@ | |
|
|
||
|
|
||
| def load_and_explore(data_dir: Path) -> tuple[pd.DataFrame, pd.DataFrame]: | ||
| """Task 2: Load both CSV files and explore their contents before cleaning.""" | ||
| # TODO: Read messy_sales.csv and messy_customers.csv with pd.read_csv(). | ||
| # TODO: For each DataFrame call .info(), .describe(), .head(20), and .isna().sum(). | ||
| # TODO: Log what you discover (e.g. which columns have nulls, any suspicious values). | ||
| raise NotImplementedError("Task 2: implement load_and_explore") | ||
| sales = pd.read_csv(data_dir / "messy_sales.csv") | ||
| customers = pd.read_csv(data_dir / "messy_customers.csv") | ||
|
|
||
| logging.info("=== SALES INFO ===") | ||
| sales.info() | ||
|
|
||
| logging.info("=== SALES DESCRIBE ===") | ||
| logging.info("\n%s", sales.describe(include="all")) | ||
|
|
||
| logging.info("=== SALES HEAD ===") | ||
| logging.info("\n%s", sales.head(20)) | ||
|
|
||
| logging.info("=== SALES MISSING VALUES ===") | ||
| logging.info("\n%s", sales.isna().sum()) | ||
|
|
||
| logging.info("=== CUSTOMERS INFO ===") | ||
| customers.info() | ||
|
|
||
| logging.info("=== CUSTOMERS DESCRIBE ===") | ||
| logging.info("\n%s", customers.describe(include="all")) | ||
|
|
||
| logging.info("=== CUSTOMERS HEAD ===") | ||
| logging.info("\n%s", customers.head(20)) | ||
|
|
||
| logging.info("=== CUSTOMERS MISSING VALUES ===") | ||
| logging.info("\n%s", customers.isna().sum()) | ||
|
|
||
| return sales, customers | ||
|
|
||
|
|
||
| def clean_sales(sales: pd.DataFrame) -> pd.DataFrame: | ||
| """Task 3: Clean the sales DataFrame using vectorized Pandas operations.""" | ||
| # TODO: Normalize product_name with .str.strip().str.title(). | ||
| # TODO: Normalize customer_email with .str.lower().str.strip(). | ||
| # TODO: Convert price to numeric with pd.to_numeric(errors="coerce"). | ||
| # TODO: Parse date with pd.to_datetime(errors="coerce"). | ||
| # TODO: Drop rows where product_name is missing. | ||
| # TODO: Drop rows where price is negative. | ||
| # TODO: Drop rows where quantity is zero. | ||
| # TODO: Drop rows where date is NaT (invalid after parsing). | ||
| # TODO: Remove duplicate transactions: .drop_duplicates(subset="transaction_id", keep="first"). | ||
| # TODO: Decide what to do with outlier prices (clip, flag, or leave) and add a comment explaining why. | ||
| raise NotImplementedError("Task 3: implement clean_sales") | ||
| sales = sales.copy() | ||
|
|
||
| sales["product_name"] = sales["product_name"].str.strip().str.title() | ||
| sales["customer_email"] = sales["customer_email"].str.lower().str.strip() | ||
| sales["price"] = pd.to_numeric(sales["price"], errors="coerce") | ||
| sales["date"] = pd.to_datetime(sales["date"], errors="coerce") | ||
|
|
||
| sales = sales[ | ||
| sales["product_name"].notna() | ||
| & (sales["product_name"] != "") | ||
| & (sales["price"] >= 0) | ||
| & (sales["quantity"] != 0) | ||
| & sales["date"].notna() | ||
| ] | ||
|
|
||
| sales = sales.drop_duplicates( | ||
| subset="transaction_id", | ||
| keep="first", | ||
| ) | ||
|
|
||
| logging.info("Cleaned sales rows: %s", len(sales)) | ||
|
|
||
| return sales | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this does not include the required outlier decision - you could have added a comment or decide to remove them using eg |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,21 +14,68 @@ | |
|
|
||
| def download_inputs(data_dir: Path) -> None: | ||
| """Task 1: Download input CSV files from Azure Blob Storage.""" | ||
| # TODO: Create a BlobServiceClient using DefaultAzureCredential and ACCOUNT_URL. | ||
| # TODO: Get a container client for SOURCE_CONTAINER. | ||
| # TODO: For each filename in FILES, download the blob and write it to data_dir/<filename>. | ||
| # TODO: Log a message for each downloaded file. | ||
| raise NotImplementedError("Task 1: implement download_inputs") | ||
| data_dir.mkdir(exist_ok=True) | ||
|
|
||
| credential = DefaultAzureCredential() | ||
| service = BlobServiceClient( | ||
| account_url=ACCOUNT_URL, | ||
| credential=credential, | ||
| ) | ||
|
|
||
| container = service.get_container_client(SOURCE_CONTAINER) | ||
|
|
||
| for filename in FILES: | ||
| blob = container.get_blob_client(filename) | ||
|
|
||
| with open(data_dir / filename, "wb") as f: | ||
| f.write(blob.download_blob().readall()) | ||
|
|
||
| logging.info("Downloaded %s", filename) | ||
|
|
||
|
|
||
| def upload_outputs(output_dir: Path, github_username: str) -> None: | ||
| """Task 7 (extra credit): Upload Parquet outputs to Azure and verify the round-trip.""" | ||
| container_name = f"week4-{github_username}" | ||
|
|
||
| # EXTRA CREDIT — implement this after Tasks 2–6 are working. | ||
| # TODO: Create a BlobServiceClient using DefaultAzureCredential and ACCOUNT_URL. | ||
| # TODO: Get (or create) the container named container_name. | ||
| # TODO: Upload every .parquet file in output_dir to the container. | ||
| # TODO: Download customer_summary.parquet back and assert its row count matches the local file. | ||
| # TODO: Log the container name and number of files uploaded. | ||
| raise NotImplementedError("Task 7: implement upload_outputs") | ||
| credential = DefaultAzureCredential() | ||
| service = BlobServiceClient( | ||
| account_url=ACCOUNT_URL, | ||
| credential=credential, | ||
| ) | ||
|
|
||
| container = service.get_container_client(container_name) | ||
|
|
||
| try: | ||
| container.create_container() | ||
| logging.info("Created container %s", container_name) | ||
| except Exception: | ||
| logging.info("Container %s already exists", container_name) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here any error is treated as “container already exists":this might hide real failures like eg you could use instead |
||
|
|
||
| parquet_files = list(output_dir.glob("*.parquet")) | ||
|
|
||
| for path in parquet_files: | ||
| blob = container.get_blob_client(path.name) | ||
|
|
||
| with open(path, "rb") as f: | ||
| blob.upload_blob(f, overwrite=True) | ||
|
|
||
| logging.info("Uploaded %s", path.name) | ||
|
|
||
| local_customer_summary = pd.read_parquet( | ||
| output_dir / "customer_summary.parquet" | ||
| ) | ||
|
|
||
| downloaded_bytes = container.get_blob_client( | ||
| "customer_summary.parquet" | ||
| ).download_blob().readall() | ||
|
|
||
| remote_customer_summary = pd.read_parquet( | ||
| io.BytesIO(downloaded_bytes) | ||
| ) | ||
|
|
||
| assert len(local_customer_summary) == len(remote_customer_summary) | ||
|
|
||
| logging.info( | ||
| "Verified customer_summary.parquet row count: %s rows", | ||
| len(local_customer_summary), | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
content is inside HTML comments, so it is effectively invisible when rendered