Answer Key
SQL, Databases & Pandas - Evaluation (Attempt 2)
CodeWithMunnaX
⬇ Download PDFSQL Window Functions
6 QsA dataset records each student's monthly test score across several months. An analyst wants to compute, for every student separately, the change in score compared to that same student's previous month -- without mixing one student's previous score into another student's row. Which window function specification correctly achieves this?
Explanation
PARTITION BY student_name confines the LAG calculation to each student's own rows, so the previous-month lookup never crosses into another student's data. Omitting PARTITION BY would let the previous row belong to a completely different student, an AVG window computes an average rather than a prior-month lag value, and a plain GROUP BY would collapse the monthly rows instead of preserving row-by-row comparisons.
A teacher lists exam scores in descending order: 88, 85, 85, 82, 80. If DENSE_RANK() is applied over this ordering, what rank is assigned to the student who scored 82?
Explanation
DENSE_RANK assigns consecutive ranks without skipping any after a tie: 88 gets rank 1, both students scoring 85 tie for rank 2, and the next distinct score (82) gets rank 3 -- not rank 4, which is what RANK would assign because it skips a rank after a tie.
A retail analyst wants to compute month-over-month sales growth for a store by comparing this month's sales to last month's sales, using a monthly sales table ordered by month. Which window function retrieves the previous month's sales value into the same row?
Explanation
LAG looks backward in the time sequence and pulls in the value from the previous row, which is exactly what is needed to compare this month against last month. LEAD instead looks forward to a future row, RANK only assigns an ordering position rather than retrieving a sales value, and GROUP BY would collapse the monthly rows entirely rather than aligning a prior month's value alongside the current one.
An analyst wants to show each employee's individual salary alongside their department's average salary, in the same row, without collapsing the individual employee rows. Which approach fits this requirement?
Explanation
The OVER clause with PARTITION BY computes the aggregate per group while appending it as a new column, keeping every original employee row intact. GROUP BY, by contrast, collapses rows into one per department, so it cannot preserve individual employee rows; SELECT DISTINCT only removes duplicate salary values and does not compute an average; sorting by salary does not compute or attach any departmental average.
An analyst wants to smooth a noisy daily rainfall time series by computing, for each day, the average of that day plus the 2 days before and 2 days after it (a 5-day window). Assume the table has exactly one row for every calendar day in the period, with no missing dates. Which statements about this task are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
The ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING window correctly captures the current day plus the two days on either side, and moving averages exist precisely to smooth out sharp fluctuations and reveal the underlying trend. This clause does require an ORDER BY inside OVER() to define the row sequence PRECEDING/FOLLOWING refers to, and it does not require any prior GROUP BY aggregation to be applied.
A company wants to rank students within each batch separately (not overall across all batches). Which of the following correctly achieve this batch-specific ranking? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
Using PARTITION BY batch correctly restarts the ranking for each batch, and this works whether RANK() (which skips ranks after ties) or DENSE_RANK() (which does not skip ranks) is used. Leaving out PARTITION BY entirely ranks across the whole dataset rather than per batch, and swapping the roles of batch and score in the partition/order clauses ranks by batch within each score value instead of the intended batch-specific score ranking.
Joins & Set Operations
5 QsA Students table is LEFT JOINed to a Marks table (Students as the left table), so every student appears even if they have no marks recorded yet. For a student with no matching row in Marks, what will the Marks-table columns show in the joined result?
Explanation
A LEFT JOIN keeps every row from the left table (Students); when there is no matching row in the right table (Marks), the right table's columns are filled with NULL instead of being dropped or replaced with a numeric placeholder like zero, and no other student's data is ever substituted in.
A streaming platform stores shows and watch_history tables. To find every show that has never been watched by any user, which querying approach correctly identifies them?
Explanation
This is the anti-join pattern: a LEFT JOIN keeps every show, and shows with no matching watch_history row will have NULL in the joined watch_history columns, so filtering for those NULLs isolates never-watched shows. An INNER JOIN would only return shows that already have at least one watch record, a CROSS JOIN produces every possible show-watch combination without filtering for absence, and combining the two tables with UNION ALL does not test for a missing relationship at all.
Two tables, logins_day1 and logins_day2, share the same structure and contain some overlapping user IDs from users who logged in on both days. To build a list of unique users who logged in across the two days, with no duplicate entries, which set operation should be used?
Explanation
UNION combines rows from both tables and removes duplicates, producing exactly the distinct set of users who logged in on either day. UNION ALL would keep the overlapping users twice, INTERSECT would return only the users common to both days, and EXCEPT would return only users present in one table but missing from the other.
A retailer wants to generate every possible combination of t-shirt type and t-shirt size to pre-populate a size chart. Which statements about the SQL operation suited for this task are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
A CROSS JOIN returns the Cartesian product of both tables, so its row count is naturally the number of types multiplied by the number of sizes, and because that output can grow very large it should be used deliberately rather than by default. Omitting the ON condition from an INNER JOIN is not the standard way to build a Cartesian product -- CROSS JOIN is the construct built for exactly this purpose.
A team wants to combine cricket_players (student_id, name, joined_date) and music_club_members (name, student_id, active_status) using a set operation such as UNION. Both tables have 3 columns, but the column order differs and the third column differs in both name and data type. Which statements about this situation are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
Set operations require matching column count, identical column names in the same order, and compatible data types; here the order differs (student_id first vs name first) and the third columns differ in both name and likely data type, so simply matching the column count is not enough -- the tables must be aligned first. It is not enough to just have the same number of columns for the operation to be guaranteed correct.
Filtering, Sorting & Basic Queries
3 QsA query runs SELECT name, age FROM students ORDER BY age; on a SQLite database, and some students have a NULL age value. Where will these students appear in the ascending output?
Explanation
In SQLite (as in MySQL, PostgreSQL, and SQL Server), NULL values are treated as the smallest possible value in ascending order, so rows with a NULL age sort to the very beginning rather than the end -- they are neither scattered randomly nor dropped from the output. (Note: not every SQL engine agrees -- Oracle sorts NULLs last in ascending order by default.)
A restaurant analyst wants to list every city that appears in a restaurants table, with each city name shown only once even though many restaurants share the same city. Which SQL keyword accomplishes this?
Explanation
DISTINCT removes duplicate values so each unique city appears only once. BETWEEN filters values within a numeric range, LIMIT restricts the number of rows returned (without removing duplicates), and ORDER BY only changes the sort order.
A student writes SELECT price * quantity AS total_cost FROM orders WHERE total_cost > 1000; expecting it to filter orders where the computed total exceeds 1000, but the database reports that total_cost is an unknown column in the WHERE clause. What causes this, and how should the query be fixed?
Explanation
SQL's execution order runs FROM and WHERE before SELECT, so any alias defined in SELECT (like total_cost) does not exist yet when WHERE is evaluated -- the fix is to repeat the full expression directly inside WHERE. SQL does support computed columns via expressions in SELECT, AS is valid alias syntax, and WHERE can filter on any expression, not only primary key columns.
CTEs & Query Structure
2 QsConsider this query:
WITH regional_sales AS (
SELECT region, SUM(amount) AS total
FROM sales
GROUP BY region
),
top_regions AS (
SELECT region, total
FROM regional_sales
WHERE total > 100000
)
SELECT region FROM top_regions ORDER BY total DESC;
What does this query ultimately return?
Explanation
The first CTE aggregates sales by region, the second CTE filters that aggregated result down to regions above 100000, and the final SELECT orders those filtered region names by total sales descending -- returning multiple qualifying regions, not raw unaggregated rows, not a syntax error (chaining CTEs where one builds on another is valid), and not only a single top region.
A query needs to (1) filter transactions from the last quarter, (2) aggregate them by region, and (3) rank regions by total revenue -- three sequential steps a teammate will later need to review and modify. Which SQL construct best fits this multi-step, readable requirement?
Explanation
Chaining CTEs lets each sequential step (filtering, then aggregating, then ranking) be written as its own named, readable stage that the next stage builds on, which is exactly the multi-step maintainability CTEs are used for. A single WHERE clause cannot perform aggregation or ranking, a self CROSS JOIN produces a Cartesian product unrelated to sequential filtering and ranking, and a trailing DISTINCT only removes duplicate rows without organizing any of the three steps.
Normalization
4 QsA campus canteen stores every order in one giant spreadsheet with columns for student name, student phone, dish name, dish price, and order date -- repeating each student's phone number and each dish's price on every order row. When a dish's price changes, the owner must edit hundreds of rows, and typos have already produced two different phone numbers for the same student. Which redesign resolves both problems at their root?
Explanation
Splitting a single overloaded table into separate logical tables linked by primary and foreign keys stores each phone number and each price exactly once, eliminating both the redundant repeated writes that cause update anomalies and the risk of the same fact being recorded inconsistently. A notes column only documents the problem without preventing it, a duplicate spreadsheet doubles the redundancy instead of removing it, and sorting only makes an existing inconsistency easier to notice, not impossible to create.
A students table has a column favorite_subjects that stores comma-separated text like 'Math, Science, Art' inside a single cell for each student. Which normalization rule does this design violate, and what fixes it?
Explanation
First Normal Form requires every cell to hold exactly one atomic value; a comma-separated list violates this, and the fix is to break the list into separate rows. Removing the column loses information rather than fixing the structure, adding a foreign key alone does not make the existing cell atomic, and merging the column into the primary key does not address non-atomic values at all.
A table uses a composite primary key of (Order_ID, Item_Name), with additional columns Order_Date and Item_Price. Order_Date depends only on Order_ID, and Item_Price depends only on Item_Name -- neither depends on the full composite key. Which normalization issue does this table have, and what fixes it?
Explanation
When a non-key column depends on only part of a composite primary key rather than the whole key, that is a partial dependency and a Second Normal Form violation, fixed by moving each such column into a table keyed by the specific part of the composite key it depends on. There is no non-atomic value here to trigger a First Normal Form issue, no reference between two unrelated attributes to enforce via a foreign key, and composite keys do not automatically guarantee any normal form.
A students_courses table stores Student_ID, Course_ID, Course_Name, and Department_Head_Name, where Course_Name and Department_Head_Name both depend only on Course_ID, not on the full Student_ID + Course_ID combination and not on Student_ID at all. Which statements about this table's normalization are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
A non-key column depending on only part of a composite primary key (here, Course_ID, which is part of the Student_ID + Course_ID composite key) rather than the whole key is a partial dependency and a Second Normal Form violation -- not a transitive dependency, which requires a non-key column to depend on another non-key column instead of on part of the key. The fix is to move Course_Name and Department_Head_Name into their own Courses table keyed by Course_ID. Having a defined primary key alone does not guarantee 2NF compliance, and separating dependent data into its own table reduces redundancy rather than increasing it.
Keys, Constraints & Data Types
4 QsA bank wants to guarantee that every customer row in its database always has a recorded balance value, never a missing one. Which SQL constraint enforces this?
Explanation
NOT NULL ensures a column can never have a missing value, which is exactly the guarantee needed for a balance column. UNIQUE only prevents duplicate values, DEFAULT only supplies a fallback when no value is given (it doesn't block an explicit NULL), and FOREIGN KEY only links a column to another table's primary key.
In a relational database, which of the following best defines a Primary Key?
Explanation
A Primary Key is a column (or set of columns) whose value uniquely identifies each record in a table, similar to how an Aadhaar or passport number uniquely identifies a person. It is not about storing large text, it cannot hold duplicate values, and it is not a mechanism for linking separate, unrelated databases.
Which of the following are numeric SQL data types used to store numbers with varying size or precision? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
INTEGER stores whole numbers, DECIMAL stores exact numeric values with decimals, and BIGINT stores integers larger than what a standard integer type can hold -- all three are numeric types. VARCHAR is a non-numeric, variable-length string type used for text, not numbers.
Which of the following statements correctly describe types of relational integrity constraints? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
Referential integrity requires foreign keys to match an existing primary key, entity integrity requires a non-null primary key on every table, and domain integrity restricts each column to its appropriate data type and format. Allowing a foreign key to point to an already-deleted row is precisely what referential integrity is designed to prevent, not something domain integrity permits.
Pandas Operations
6 QsA data scientist runs df['dept_avg_salary'] = df.groupby('department')['salary'].transform('mean') on a DataFrame with 500 employee rows spread across 5 departments. How many rows will the resulting DataFrame have, and what will the new column contain?
Explanation
transform() creates a new column without reducing the row count, so all 500 rows remain, and each row's new column holds the average salary of the department that row belongs to, not a single collapsed table, not an overall dataset-wide average, and not an expanded cross-product of departments and employees.
A pandas DataFrame employees needs to be filtered to rows where salary is above 80000 AND department is 'Sales'. A student writes: employees[employees['salary'] > 80000 and employees['department'] == 'Sales']. What is the most likely outcome, and what should be corrected?
Explanation
Combining multiple row conditions in pandas requires the & operator with each condition wrapped in parentheses, not Python's and keyword, because and is not defined for element-wise comparison across an entire Series and raises an error rather than silently succeeding, returning everything, or returning only a column.
In pandas, pd.merge(orders, customers, on='customer_id', how='left') combines two DataFrames. Which statement correctly describes the behavior of how='left'?
Explanation
A left merge keeps every row from the left DataFrame (orders here) and fills in NaN wherever no matching customer_id exists in customers. Keeping only matching rows describes an inner merge, preserving every row of the right table instead describes a right merge, and keeping every row from both regardless of matches describes an outer merge.
Which of the following statements about combining two DataFrames in pandas are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
pd.concat() stacks rows from both DataFrames and behaves like UNION ALL by default (keeping duplicates), calling drop_duplicates() afterward removes those duplicates to reproduce UNION, and isin() identifies elements common to both DataFrames the way INTERSECT does. The EXCEPT-style comparison (rows present in one table but not the other) is instead achieved with filtering logic, not with pd.merge().
A dataset needs a new salary_band column: 'High' for salary above 100000, 'Senior' for 70000-100000, 'Mid' for 40000-69999, and 'Junior' otherwise. Which statements about implementing this with np.select() are correct? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
np.select() evaluates a list of conditions against a matching list of values and applies the first one that is True, and it supports a default value for rows matching none of them -- exactly what handling the 'Junior' fallback needs. It is designed to handle more than two conditions in one call (that is its whole purpose versus the simpler, binary-condition np.where()), so it is not limited to only two conditions.
A dataset has a city column, and an analyst wants to filter for cities that start with 'B' or contain 'pur' anywhere in the name, in a case-insensitive way. Which of the following are valid building blocks for this task? (Select all that apply)
Note: This is a Multiple Select Question (MSQ). The answer is marked incorrect if any correct option is missed or any incorrect option is selected.
Explanation
str.startswith('B') checks for cities beginning with 'B', str.contains('pur', case=False) finds cities containing 'pur' case-insensitively, and str.lower() is a valid way to normalize case before comparing strings. isna() checks for missing values and has nothing to do with detecting a starting letter.
Case Studies (Subjective)
2 QsProblem Statement
A college's academic office maintains two tables: students (every enrolled student) and marks (every exam attempt a student has made). Some students have not yet appeared in marks at all -- they have no exam records yet. The academic office wants a single set of SQL queries that gives them a complete, accurate picture of exam performance across the entire student body, including the students who have not taken any exam yet.
Dataset
Run the following in any SQL environment (SQLite, MySQL, or PostgreSQL syntax all work with minor adjustments) to set up the tables:
CREATE TABLE students (
student_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
city TEXT NOT NULL
);
INSERT INTO students VALUES (1, 'Priya', 'Delhi');
INSERT INTO students VALUES (2, 'Rahul', 'Mumbai');
INSERT INTO students VALUES (3, 'Anjali', 'Delhi');
INSERT INTO students VALUES (4, 'Ketan', 'Pune');
INSERT INTO students VALUES (5, 'Vamsi', 'Mumbai');
INSERT INTO students VALUES (6, 'Meera', 'Delhi');
CREATE TABLE marks (
marks_id INTEGER PRIMARY KEY,
student_id INTEGER,
course_id INTEGER,
score INTEGER,
attendance INTEGER
);
INSERT INTO marks VALUES (101, 1, 10, 78, 90);
INSERT INTO marks VALUES (102, 1, 11, 85, 88);
INSERT INTO marks VALUES (103, 2, 10, 62, 70);
INSERT INTO marks VALUES (104, 3, 10, 91, 95);
INSERT INTO marks VALUES (105, 3, 11, 74, 80);
INSERT INTO marks VALUES (106, 5, 11, 55, 60);
Notice that Ketan (student_id 4) and Meera (student_id 6) have no rows in marks at all -- your queries must handle both correctly.
Tasks
- Write a single SQL query that lists every student's name alongside their exam score, ensuring that students with no marks record still appear in the result (their score should show as NULL rather than the student being dropped).
- Write a query that computes, for each
course_id, the total number of exam attempts and the average score, using the appropriate aggregate functions. - Using a Common Table Expression (CTE), first build the joined student+marks result from Task 1 as a named CTE, then write a main query on top of that CTE that computes the average score for each city (again, students with no marks should still appear in their city's grouping (their row will have a NULL score, which AVG() automatically skips when computing that city's average)).
Submission Format
Paste all three SQL queries in your response, clearly labeled Task 1, Task 2, and Task 3.
Note: You may use any IDE (online: Google Colab, Jupyter Notebook; offline: VS Code, PyCharm, or similar) to write, run, and test your code. You must disable all AI-based extensions (GitHub Copilot, Tabnine, Codeium, etc.) before starting. AI tools of any kind are not permitted and will lead to disqualification. Submit your complete code as a copy-pasted snippet in the input box — do not submit a URL, notebook link, GitHub repository, or Google Drive file.
Model answer
Question Explanation
This question tests whether a student can combine three separate SQL skills into one coherent workflow: preserving unmatched rows with a LEFT JOIN, summarizing data per group with GROUP BY and aggregate functions, and organizing a multi-step query for readability with a CTE.
Short Explanation
Use a LEFT JOIN with students as the left table so every student appears even without exam records, use GROUP BY with COUNT and AVG for the per-course summary, then wrap the Task 1 join in a CTE and re-aggregate by city on top of it.
Long Explanation
For Task 1, LEFT JOIN students to marks on student_id so every student row is preserved; any student without a matching marks row will show NULL for the score column instead of being excluded. For Task 2, GROUP BY course_id on the marks table and apply COUNT() to count attempts and AVG() to compute the mean score per course. For Task 3, wrap the Task 1 join inside a CTE using WITH ... AS (...), selecting city and score from the joined result; then in the main query, SELECT FROM that CTE, GROUP BY city, and apply AVG(score) -- this keeps the query readable as two clearly separated stages (build the joined base, then aggregate) instead of one large nested query.
Model Solution
-- Task 1: every student with their score, including students with no marks
SELECT s.name, m.score
FROM students AS s
LEFT JOIN marks AS m
ON s.student_id = m.student_id;
-- Task 2: attempts and average score per course
SELECT course_id,
COUNT(student_id) AS total_attempts,
AVG(score) AS avg_score
FROM marks
GROUP BY course_id;
-- Task 3: average score per city, using a CTE built on Task 1's join
WITH student_scores AS (
SELECT s.city, m.score
FROM students AS s
LEFT JOIN marks AS m
ON s.student_id = m.student_id
)
SELECT city, AVG(score) AS avg_city_score
FROM student_scores
GROUP BY city;
Problem Statement
A retail chain tracks monthly sales for each of its stores. The data team wants to build feature-engineered columns from this raw monthly sales data to eventually feed a forecasting model. You are asked to reproduce, in pandas, the row-preserving feature engineering that would otherwise be done in SQL with PARTITION BY and LAG.
Dataset
| store_id | month | sales |
|---|---|---|
| S1 | 2026-01 | 100000 |
| S1 | 2026-02 | 110000 |
| S1 | 2026-03 | 105000 |
| S1 | 2026-04 | 120000 |
| S2 | 2026-01 | 80000 |
| S2 | 2026-02 | 78000 |
| S2 | 2026-03 | 95000 |
| S2 | 2026-04 | 90000 |
Ready-to-run initializer:
import pandas as pd
sales_data = pd.DataFrame({
'store_id': ['S1', 'S1', 'S1', 'S1', 'S2', 'S2', 'S2', 'S2'],
'month': ['2026-01', '2026-02', '2026-03', '2026-04', '2026-01', '2026-02', '2026-03', '2026-04'],
'sales': [100000, 110000, 105000, 120000, 80000, 78000, 95000, 90000]
})
Tasks
- Add a column
store_avg_salescontaining each store's average sales across all 4 of its months, usinggroupby().transform(), so the row count stays at 8 (no reduction). - Add a column
prev_month_salescontaining the previous month's sales for that same store (a missing value for each store's first month), usingshift()applied within each store's own group so no store's lag value ever comes from a different store. - Add a column
pct_change_from_prevcomputing the percentage change ofsalesversusprev_month_salesfor each row, leaving it as a missing value whereverprev_month_salesis missing.
Expected Output
The final DataFrame should have 8 rows and the columns: store_id, month, sales, store_avg_sales, prev_month_sales, pct_change_from_prev.
Submission Format
Paste your complete pandas code as a copy-pasted snippet in the input box.
Note: You may use any IDE (online: Google Colab, Jupyter Notebook; offline: VS Code, PyCharm, or similar) to write, run, and test your code. You must disable all AI-based extensions (GitHub Copilot, Tabnine, Codeium, etc.) before starting. AI tools of any kind are not permitted and will lead to disqualification. Submit your complete code as a copy-pasted snippet in the input box — do not submit a URL, notebook link, GitHub repository, or Google Drive file.
Model answer
Question Explanation
This question tests whether a student can chain two pandas feature-engineering techniques -- groupby().transform() for a per-group aggregate column, and a grouped shift() for a lag feature -- which together reproduce what SQL would express with OVER/PARTITION BY and LAG.
Short Explanation
Use groupby('store_id')['sales'].transform('mean') for the per-store average, groupby('store_id')['sales'].shift(1) for the previous month's sales, then compute the percentage change from the shifted column using vectorized arithmetic.
Long Explanation
For Task 1, group by store_id and call .transform('mean') on the sales column; transform preserves the original row count by broadcasting the group's average back onto every row of that group, unlike a plain groupby().mean() which would collapse to one row per store. For Task 2, group by store_id again and call .shift(1) on sales; because the shift happens after grouping, each store's own previous-month value is retrieved without ever crossing into another store's rows, and the first month of each store naturally becomes a missing value since there is no prior row to shift from. For Task 3, compute (sales - prev_month_sales) / prev_month_sales for each row using ordinary vectorized pandas arithmetic; wherever prev_month_sales is a missing value, the arithmetic naturally propagates a missing result rather than raising an error.
Model Solution
import pandas as pd
sales_data = pd.DataFrame({
'store_id': ['S1', 'S1', 'S1', 'S1', 'S2', 'S2', 'S2', 'S2'],
'month': ['2026-01', '2026-02', '2026-03', '2026-04', '2026-01', '2026-02', '2026-03', '2026-04'],
'sales': [100000, 110000, 105000, 120000, 80000, 78000, 95000, 90000]
})
# Task 1: per-store average sales, row count preserved
sales_data['store_avg_sales'] = sales_data.groupby('store_id')['sales'].transform('mean')
# Task 2: previous month's sales, computed within each store's own group
sales_data['prev_month_sales'] = sales_data.groupby('store_id')['sales'].shift(1)
# Task 3: percentage change vs previous month
sales_data['pct_change_from_prev'] = (
(sales_data['sales'] - sales_data['prev_month_sales']) / sales_data['prev_month_sales']
) * 100
print(sales_data)