Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Pandas›Quiz

Pandas quiz

Ten questions at a time, drawn from 170. Every answer is explained. Nothing is saved and no account is needed.

Question 1 of 10Score 0

Which of the following operations best utilizes the vectorized nature of Pandas?

Practice quiz for Pandas. Scores are not saved.

Study first?

Every question comes from a lesson in the Pandas course.

Read the course →

Interview prep

Written questions with full answers.

Pandas interview questions →

All Pandas quiz questions and answers

  1. Which of the following operations best utilizes the vectorized nature of Pandas?

    • for row in df.iterrows(): df.at[row[0], 'A'] = row[1]['B'] * 2
    • df['A'] = df['B'] * 2
    • df.apply(lambda x: x['B'] * 2, axis=1)
    • df.values.tolist() = [x * 2 for x in df['B']]

    Answer: df['A'] = df['B'] * 2. Direct arithmetic on the Series (df['B'] * 2) is vectorized and performs at C-speed. iterrows is slow, apply is slightly better but still uses Python overhead, and converting to lists discards Pandas optimization.

    From lesson: Introduction to Pandas and Data Analysis

  2. You have a DataFrame 'df' with an index of dates. How do you select the row corresponding to '2023-01-01' using labels?

    • df.iloc['2023-01-01']
    • df.ix['2023-01-01']
    • df.loc['2023-01-01']
    • df.get('2023-01-01')

    Answer: df.loc['2023-01-01']. .loc is designed for label-based indexing. .iloc only accepts integers. .ix is deprecated and removed. .get is for dictionary-style access.

    From lesson: Introduction to Pandas and Data Analysis

  3. What is the primary difference between a Series and a DataFrame?

    • A Series is 2D, while a DataFrame is 3D.
    • A Series is a single column of data, while a DataFrame is a collection of Series arranged as a table.
    • A Series contains only integers, while a DataFrame contains only strings.
    • A Series is mutable, but a DataFrame is immutable.

    Answer: A Series is a single column of data, while a DataFrame is a collection of Series arranged as a table.. A Series is the base 1D object, while a DataFrame is the container for multiple Series (columns). Both can hold various data types and are mutable.

    From lesson: Introduction to Pandas and Data Analysis

  4. Why does the 'SettingWithCopyWarning' typically occur?

    • Because the user is trying to delete a column that does not exist.
    • Because the user is attempting to modify a slice of a DataFrame that might be a view rather than a copy.
    • Because the user loaded a CSV file with duplicate column names.
    • Because the index of the DataFrame is not unique.

    Answer: Because the user is attempting to modify a slice of a DataFrame that might be a view rather than a copy.. The warning triggers when Pandas cannot determine if the operation is modifying the original object or a temporary slice, often when chaining indices. The others describe unrelated errors.

    From lesson: Introduction to Pandas and Data Analysis

  5. If you perform 'df['price'].fillna(0, inplace=True)', what is the result?

    • The original 'price' column is updated to replace NaN with 0.
    • A new column 'price' is created and the old one remains NaN.
    • The entire DataFrame is cleared of all NaN values.
    • The operation fails because inplace=True is not allowed on a single column.

    Answer: The original 'price' column is updated to replace NaN with 0.. Setting inplace=True modifies the specified object directly. Because we targeted df['price'], that specific Series is updated. The other options misrepresent how inplace updates work.

    From lesson: Introduction to Pandas and Data Analysis

  6. You have two Series, s1 and s2, with different indices. When you execute 's1 + s2', what happens to the result?

    • It raises a ValueError due to size mismatch.
    • It aligns values by their labels, producing NaN for any missing indices.
    • It ignores the index and adds them based on their position in the Series.
    • It returns only the intersection of the two indices.

    Answer: It aligns values by their labels, producing NaN for any missing indices.. Pandas performs an outer join on indices. Options 1 and 3 are incorrect because Pandas prioritizes index alignment over position or size. Option 4 is incorrect because outer joins include all unique labels from both sides, not just the intersection.

    From lesson: Series — Creation and Operations

  7. If you initialize a Series with a dictionary {'a': 1, 'b': 2} and provide index=['b', 'c'], what is the result?

    • A Series with indices ['a', 'b'] and values [1, 2].
    • A Series with index 'b' having value 2 and index 'c' being NaN.
    • A Series with indices ['a', 'b', 'c'] and values [1, 2, NaN].
    • A ValueError indicating the index does not match the dictionary keys.

    Answer: A Series with index 'b' having value 2 and index 'c' being NaN.. When an index is provided, Pandas filters the dictionary by the provided index. 'a' is discarded because it isn't in the requested index, and 'c' is added as NaN because it isn't in the dictionary. Other options fail to account for this explicit filtering.

    From lesson: Series — Creation and Operations

  8. What is the primary difference between accessing a Series using s[0] and s.iloc[0]?

    • There is no difference.
    • s[0] is for labels, while s.iloc[0] is for integer positions.
    • s[0] checks for both integer positions and labels, which can cause ambiguity if the index is numeric.
    • s.iloc[0] is faster than s[0].

    Answer: s[0] checks for both integer positions and labels, which can cause ambiguity if the index is numeric.. Pandas index-based access 's[0]' is ambiguous if the index contains integers, as it tries to find the label first. '.iloc' explicitly forces integer position selection, eliminating this ambiguity. Options 1 and 4 are incorrect, and Option 2 is incomplete regarding the behavior of labels.

    From lesson: Series — Creation and Operations

  9. Which of the following best describes the outcome of using the 'name' attribute on a Series object?

    • It renames the index labels of the Series.
    • It renames the Series object itself in memory.
    • It acts as a label for the data, which becomes the column name if converted to a DataFrame.
    • It provides a unique identifier used to filter the Series.

    Answer: It acts as a label for the data, which becomes the column name if converted to a DataFrame.. The 'name' attribute identifies the data itself. When multiple Series are combined into a DataFrame, the 'name' attribute becomes the column header. The other options confuse the series name with the index name or internal memory references.

    From lesson: Series — Creation and Operations

  10. How does vectorization affect operations performed on a Pandas Series compared to a Python list?

    • It allows operations to be applied to the entire Series at once without manual loops.
    • It forces the Series to be converted to a list before calculation.
    • It increases execution time by checking every index for every operation.
    • It is only available for numerical data types.

    Answer: It allows operations to be applied to the entire Series at once without manual loops.. Vectorization utilizes highly optimized C-code to perform operations on the underlying data buffers simultaneously, avoiding slow Python loops. Other options are incorrect because vectorization is optimized for speed, does not require conversion, and supports various data types including strings.

    From lesson: Series — Creation and Operations

  11. When creating a DataFrame from a dictionary of lists, what happens if the lists are of unequal length?

    • The DataFrame automatically pads the shorter lists with NaN values
    • The DataFrame is created using only the shortest list length
    • It raises a ValueError as all arrays must be of the same length
    • The DataFrame repeats values in the shorter list to match the longest one

    Answer: It raises a ValueError as all arrays must be of the same length. Pandas requires that all lists in a dictionary be of equal length because a DataFrame represents a rectangular structure. Options 1, 2, and 4 are incorrect because Pandas does not implicitly pad, truncate, or repeat data during construction.

    From lesson: DataFrames — Creation and Basics

  12. Which of the following is the most efficient way to access a specific column by name in a DataFrame named 'df'?

    • df.loc[:, 'column_name']
    • df['column_name']
    • df.column_name
    • df.iloc[:, 1]

    Answer: df['column_name']. Bracket notation df['column_name'] is the standard and safest way to access columns. Using dot notation (option 2) can fail if the column name conflicts with DataFrame methods or contains spaces. Option 0 and 3 are for position/label indexing, which are less direct than key-based access.

    From lesson: DataFrames — Creation and Basics

  13. If you initialize a DataFrame with an empty list, what is the default behavior regarding columns?

    • It creates a DataFrame with one empty column labeled '0'
    • It creates a DataFrame with no columns and no index
    • It creates a DataFrame with one empty row
    • It raises an error because the structure is ambiguous

    Answer: It creates a DataFrame with no columns and no index. An empty list passed to the constructor creates an empty DataFrame. Option 0 and 2 are wrong because there is no data to infer dimensions. Option 3 is wrong because the constructor successfully initializes an empty container.

    From lesson: DataFrames — Creation and Basics

  14. What is the primary function of the 'index' parameter when creating a DataFrame from a list of dictionaries?

    • It specifies the row labels for the data rows
    • It renames the columns of the resulting DataFrame
    • It sorts the resulting DataFrame by the chosen index
    • It forces the DataFrame to only keep matching keys

    Answer: It specifies the row labels for the data rows. The 'index' parameter assigns custom labels to rows, whereas columns are derived from dictionary keys. Options 1, 2, and 3 are incorrect because they refer to column naming, sorting, or filtering, which are different processes.

    From lesson: DataFrames — Creation and Basics

  15. Why is it often preferred to use a list of dictionaries over a dictionary of lists when building a DataFrame row-by-row?

    • A list of dictionaries is significantly faster for memory allocation
    • It allows rows to have different keys, which Pandas will resolve with NaN
    • It is the only way to support non-numeric data types
    • It automatically transposes the data for better performance

    Answer: It allows rows to have different keys, which Pandas will resolve with NaN. Using a list of dictionaries is flexible because it allows missing keys (which Pandas handles by inserting NaN), making it safer for heterogeneous data. Option 0 is wrong as dictionary of lists is usually faster. Options 2 and 3 are irrelevant to the structure.

    From lesson: DataFrames — Creation and Basics

  16. When loading a CSV that uses a semicolon instead of a comma as a separator, which approach ensures the data is parsed correctly?

    • Use the sep=';' parameter in read_csv
    • Rename the file extension to .tsv
    • Set the encoding parameter to 'semicolon'
    • Use the delimiter='none' parameter

    Answer: Use the sep=';' parameter in read_csv. The 'sep' parameter explicitly defines the character separating the fields. The other options are either irrelevant (renaming extensions) or invalid parameters.

    From lesson: Reading Data: CSV, Excel, JSON, SQL

  17. A JSON file has a structure where every record is nested under a single key 'employees'. Which method is best to convert this into a flat DataFrame?

    • read_json('file.json', orient='index')
    • read_json('file.json') and then transpose
    • json_normalize(data, record_path='employees')
    • read_csv('file.json')

    Answer: json_normalize(data, record_path='employees'). json_normalize is designed to flatten nested structures. read_json will not automatically flatten the 'employees' key correctly, and read_csv is not intended for JSON format.

    From lesson: Reading Data: CSV, Excel, JSON, SQL

  18. When querying a database with 'read_sql', why should you prefer parameterized queries (using 'params') over f-strings for filtering data?

    • It makes the code run faster
    • It protects against SQL injection attacks
    • It is the only way to import datetime objects
    • It allows you to read more rows at once

    Answer: It protects against SQL injection attacks. Parameterized queries sanitize input to prevent malicious SQL injections. The other options are incorrect, as they do not affect performance or functionality in this way.

    From lesson: Reading Data: CSV, Excel, JSON, SQL

  19. If you read an Excel file using 'pd.read_excel(file, sheet_name=None)', what is the returned object type?

    • A single DataFrame
    • A list of column names
    • A dictionary of DataFrames
    • A Series object containing all sheets

    Answer: A dictionary of DataFrames. Setting 'sheet_name=None' instructs Pandas to read all sheets and return them as a dictionary where keys are sheet names and values are DataFrames. The other options describe single objects or incorrect types.

    From lesson: Reading Data: CSV, Excel, JSON, SQL

  20. What happens if you provide a 'usecols' argument to 'read_csv'?

    • Pandas reads only the specified columns, reducing memory usage
    • Pandas reads all columns but hides the ones not listed
    • Pandas deletes the columns not in the list from the source file
    • Pandas generates an error if the columns are not in the first row

    Answer: Pandas reads only the specified columns, reducing memory usage. usecols allows for memory-efficient loading by only pulling required data into RAM. The other choices incorrectly describe data manipulation or file-modifying behaviors.

    From lesson: Reading Data: CSV, Excel, JSON, SQL

  21. When exporting a DataFrame to a CSV file, why is it considered best practice to set index=False?

    • It speeds up the writing process significantly by skipping the index conversion.
    • It prevents the numeric row index from being saved as an extra, often meaningless column in the file.
    • It forces the CSV to use tab-separation instead of commas.
    • It is required to preserve the original column names correctly.

    Answer: It prevents the numeric row index from being saved as an extra, often meaningless column in the file.. Setting index=False prevents the Pandas row index from being exported as a column. Option 0 is wrong because index processing has a negligible impact on speed. Option 2 is wrong because the index is independent of the separator. Option 3 is wrong because the index has no effect on column names.

    From lesson: Writing and Exporting Data

  22. You have a DataFrame with mixed data types and need to save it for a colleague who uses software that requires standard UTF-8 encoding. Which argument should you use with to_csv?

    • compression='gzip'
    • mode='a'
    • encoding='utf-8-sig'
    • quoting=0

    Answer: encoding='utf-8-sig'. encoding='utf-8-sig' ensures characters are encoded correctly for Excel and other external software. Option 0 compresses the file, which doesn't address encoding. Option 1 appends to a file rather than setting encoding. Option 3 relates to how quotes are handled around strings, not the character set.

    From lesson: Writing and Exporting Data

  23. If you need to update a specific sheet within an existing Excel file without overwriting the entire workbook, what approach must you take?

    • Simply use df.to_excel() with the filename and the sheet name.
    • Use an ExcelWriter object as a context manager and read the existing file first.
    • Change the mode to 'r+' in the to_excel function.
    • Pandas cannot perform partial updates to Excel files; you must recreate the file.

    Answer: Use an ExcelWriter object as a context manager and read the existing file first.. To preserve other sheets, you use ExcelWriter to load the existing file structure before writing the new sheet. Option 0 overwrites the file. Option 2 is not a valid mode for to_excel. Option 3 is incorrect as the functionality exists via the engine.

    From lesson: Writing and Exporting Data

  24. Why might you choose to export a large DataFrame to Parquet format rather than CSV?

    • CSV files are always larger and cannot be read by other software.
    • Parquet is a binary format that maintains data types and compression, making it faster to read/write.
    • Parquet is the only format that allows column filtering during the read process.
    • CSV files do not support strings, whereas Parquet does.

    Answer: Parquet is a binary format that maintains data types and compression, making it faster to read/write.. Parquet is a highly efficient columnar storage format. Option 0 is false. Option 2 is false as other formats support filtering. Option 3 is false as CSV supports strings.

    From lesson: Writing and Exporting Data

  25. What happens if you run df.to_csv('data.csv') multiple times in a script without changing the filename?

    • Pandas automatically appends the new data to the existing file.
    • The operation will raise a FileExistsError automatically.
    • The existing 'data.csv' file is overwritten with the new contents of the DataFrame.
    • The script will crash because the file is locked by the previous write operation.

    Answer: The existing 'data.csv' file is overwritten with the new contents of the DataFrame.. By default, the write mode is 'w' (write), which overwrites existing files. Option 0 is incorrect because the mode must be 'a' to append. Option 1 is false. Option 3 is false as the system closes the file handle after each operation.

    From lesson: Writing and Exporting Data

  26. Given a DataFrame 'df', which code correctly selects the 'Name' column and returns a Series?

    • df[['Name']]
    • df.loc[:, 'Name']
    • df.iloc[:, 'Name']
    • df['Name',]

    Answer: df.loc[:, 'Name']. df.loc[:, 'Name'] correctly uses labels to select all rows in the 'Name' column. df[['Name']] returns a DataFrame, not a Series. df.iloc[:, 'Name'] fails because iloc requires integer positions for columns. df['Name',] is invalid syntax.

    From lesson: Selecting Columns and Rows

  27. If you want to select rows by integer position 5 through 10 (excluding 10), which syntax is correct?

    • df.iloc[5:10]
    • df.loc[5:10]
    • df[5:10]
    • df.iloc[5:11]

    Answer: df.iloc[5:10]. df.iloc[5:10] follows standard Python integer slicing rules where the start is inclusive and the stop is exclusive. df.loc uses labels, not positions. df[5:10] is ambiguous for row selection and df.iloc[5:11] would include index 10.

    From lesson: Selecting Columns and Rows

  28. What happens when you execute df[['A', 'B']]?

    • A Series containing columns A and B is returned.
    • A DataFrame containing columns A and B is returned.
    • An error is raised because double brackets are not supported.
    • Only column A is returned.

    Answer: A DataFrame containing columns A and B is returned.. Passing a list of column names inside brackets returns a new DataFrame containing only those columns. Option 1 is wrong because a list of columns results in a DataFrame, not a Series. Options 3 and 4 are factually incorrect regarding Pandas syntax.

    From lesson: Selecting Columns and Rows

  29. When using df.loc['row1':'row3', 'col1'], what is the result?

    • Only row1 and row3 are selected.
    • Rows 'row1', 'row2', and 'row3' are included if they exist.
    • Only row1, row2, and row3 are selected, and the result is a DataFrame.
    • An error occurs because labels cannot be sliced.

    Answer: Rows 'row1', 'row2', and 'row3' are included if they exist.. Label-based slicing with .loc is inclusive of the end point, so it retrieves rows from 'row1' through 'row3'. It returns a Series because only one column is selected. Option 1 ignores 'row2'. Option 3 is wrong because it returns a Series. Option 4 is false because label slicing is valid.

    From lesson: Selecting Columns and Rows

  30. How do you select rows where the 'Age' column is greater than 30 using boolean indexing?

    • df.loc[df['Age'] > 30]
    • df.iloc[df['Age'] > 30]
    • df[df.Age > 30]
    • Both 1 and 3 are correct.

    Answer: Both 1 and 3 are correct.. Both df.loc[df['Age'] > 30] and df[df.Age > 30] are valid and common ways to filter rows based on a condition. df.iloc cannot handle boolean Series directly as it expects integer positions. Therefore, both 1 and 3 are correct ways to achieve the result.

    From lesson: Selecting Columns and Rows

  31. Given a DataFrame 'df' with an index of [10, 20, 30], what does df.loc[10:20] return?

    • Only the row with index 10
    • The rows with indices 10 and 20
    • The rows with indices 10, 20, and 30
    • An error, because 10:20 is not a valid slice

    Answer: The rows with indices 10 and 20. loc is label-inclusive, so it includes both the start and end labels. Option 0 is wrong because it ignores the end. Option 2 is wrong because 30 is outside the slice. Option 3 is wrong because numeric indices are valid in loc.

    From lesson: loc vs iloc — Label vs Position Indexing

  32. What is the primary difference between df.loc[0] and df.iloc[0] if the DataFrame index is a string?

    • They both return the first row
    • iloc returns the first row; loc will raise a KeyError if 0 is not a label
    • loc returns the first row; iloc raises an error
    • They return the same thing because Pandas aliases them

    Answer: iloc returns the first row; loc will raise a KeyError if 0 is not a label. iloc uses positional index (0 is always the first row). loc looks for the literal string '0' as an index label; if that label doesn't exist, it raises a KeyError. The other options are incorrect because they ignore the strict distinction between position and label.

    From lesson: loc vs iloc — Label vs Position Indexing

  33. You want to select the first 3 rows and first 2 columns of a DataFrame. Which is the correct approach?

    • df.loc[0:2, 0:1]
    • df.iloc[0:3, 0:2]
    • df.iloc[0:2, 0:1]
    • df.loc[0:3, 0:2]

    Answer: df.iloc[0:3, 0:2]. iloc uses integer positions and is exclusive of the stop index (0 to 2 for rows means index 0, 1, 2). Option 0 and 3 use loc, which would look for literal labels '0' and '2'. Option 2 only selects 2 rows, not 3.

    From lesson: loc vs iloc — Label vs Position Indexing

  34. If you have a DataFrame where the index is [5, 4, 3, 2, 1], what does df.iloc[0:2] return?

    • Rows with indices 5 and 4
    • Rows with indices 1 and 2
    • Rows with indices 5, 4, and 3
    • An error, because the index is not sorted

    Answer: Rows with indices 5 and 4. iloc relies purely on the internal integer position of the data. The first two rows in the current order are 5 and 4, regardless of their label values. The other options confuse position with label sorting.

    From lesson: loc vs iloc — Label vs Position Indexing

  35. Which of the following is true regarding label-based selection using loc?

    • It is always faster than iloc for large datasets
    • It supports boolean arrays for masking
    • It only accepts single labels, never lists
    • It automatically converts integer labels to positions

    Answer: It supports boolean arrays for masking. loc is specifically designed to handle boolean masks where True values select the corresponding rows. Option 0 is false as performance varies. Option 2 is false as loc accepts lists. Option 3 is false as loc never treats labels as positions.

    From lesson: loc vs iloc — Label vs Position Indexing

  36. Given df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}), which syntax correctly selects rows where 'A' is 2 and 'B' is 5?

    • df[df['A'] == 2 and df['B'] == 5]
    • df[(df['A'] == 2) & (df['B'] == 5)]
    • df.loc[df['A'] == 2 | df['B'] == 5]
    • df[df['A'] == 2 & df['B'] == 5]

    Answer: df[(df['A'] == 2) & (df['B'] == 5)]. Option 2 uses the bitwise & operator with correct parentheses, which is required for element-wise boolean logic. Option 1 fails because 'and' is not vectorized. Option 3 fails because the bitwise operator precedence is wrong without parentheses. Option 4 fails because the bitwise & is evaluated before the == comparisons, causing a TypeError.

    From lesson: Boolean Indexing and Filtering

  37. What is the primary purpose of the .isin() method in Pandas filtering?

    • To check if a column contains any missing values (NaN).
    • To filter rows where a column value matches any element in a provided list.
    • To replace all values in a column with a new set of mapped values.
    • To check if the index of the DataFrame is present in another object.

    Answer: To filter rows where a column value matches any element in a provided list.. The .isin() method evaluates each element in a Series to see if it exists within a passed collection, returning a boolean mask. It is not used for checking NaNs (which uses .isna()), value replacement (which uses .replace()), or index membership exclusively.

    From lesson: Boolean Indexing and Filtering

  38. How does the '~' operator behave when applied to a boolean mask in Pandas?

    • It converts True values to False and False values to True.
    • It deletes all rows that evaluate to True in the mask.
    • It sorts the DataFrame based on the truth values of the mask.
    • It performs a logical 'and' operation across columns.

    Answer: It converts True values to False and False values to True.. The tilde (~) is the bitwise NOT operator, which performs element-wise inversion of a boolean mask. It does not delete rows (that requires indexing), it does not sort data, and it is a unary operator, not a logical 'and' operator.

    From lesson: Boolean Indexing and Filtering

  39. When you execute df.loc[df['val'] > 10, 'status'] = 'High', what is the specific effect on the DataFrame?

    • It creates a new copy of the DataFrame with the modified values.
    • It returns a Series of the status column only for filtered rows.
    • It modifies the existing DataFrame in-place at the rows where 'val' > 10 and the 'status' column.
    • It raises an error because you cannot assign to a filtered selection.

    Answer: It modifies the existing DataFrame in-place at the rows where 'val' > 10 and the 'status' column.. Using .loc with a row and column label selector allows direct, safe modification of the underlying data for the specified subset. It is preferred over chained indexing (df[mask]['col'] = x) because it avoids the SettingWithCopyWarning and ensures the original DataFrame is updated.

    From lesson: Boolean Indexing and Filtering

  40. If you apply a boolean mask to a DataFrame using df[mask], what happens if the index of the mask does not align with the DataFrame?

    • Pandas will automatically reindex the DataFrame to match the mask.
    • Pandas will ignore the mask and return the original DataFrame.
    • Pandas will raise a ValueError if the mask length does not match the DataFrame length.
    • Pandas will return a DataFrame filled with NaN values.

    Answer: Pandas will raise a ValueError if the mask length does not match the DataFrame length.. Pandas requires the boolean mask to have the same length as the DataFrame's axis when passed to the bracket operator. If the lengths differ, it cannot determine which rows to keep, resulting in a ValueError. It does not automatically reindex or fill with NaNs.

    From lesson: Boolean Indexing and Filtering

  41. Which of the following is the correct way to filter a DataFrame where column 'A' is greater than 10 AND column 'B' is less than 5?

    • df.query('A > 10 and B < 5')
    • df.query('A > 10 & B < 5')
    • df.query('A > 10', 'B < 5')
    • df.query(A > 10 && B < 5)

    Answer: df.query('A > 10 & B < 5'). Option 2 is correct because query() uses bitwise operators (&, |) for logical filtering. Option 1 is incorrect because 'and' is a Python keyword, not a valid vectorized operator in query. Option 3 is syntactically invalid. Option 4 uses C-style logical operators not supported in Pandas.

    From lesson: Conditional Selection with query()

  42. You have a variable `limit = 100` in your script. How do you correctly filter the DataFrame for rows where 'value' column exceeds this limit?

    • df.query('value > limit')
    • df.query('value > @limit')
    • df.query('value > {limit}')
    • df.query('value > self.limit')

    Answer: df.query('value > @limit'). Option 2 is correct because '@' signals to query() to look for the variable in the environment. Option 1 will fail as it treats 'limit' as a column. Option 3 is a common misunderstanding of f-string formatting. Option 4 is incorrect as 'self' does not point to the script scope.

    From lesson: Conditional Selection with query()

  43. What is the primary advantage of using query() over standard boolean indexing (e.g., df[df['A'] > 0])?

    • It is always significantly faster for small DataFrames.
    • It allows for more concise syntax and easier string-based expressions.
    • It is the only way to filter by multiple conditions.
    • It automatically reindexes the DataFrame.

    Answer: It allows for more concise syntax and easier string-based expressions.. Option 2 is the intended advantage: cleaner, more readable syntax for complex conditions. Option 1 is false as overhead exists for small data. Option 3 is false as boolean indexing handles multiple conditions fine. Option 4 is false as query() does not reindex.

    From lesson: Conditional Selection with query()

  44. How do you handle a column named 'Total Revenue' inside a query expression?

    • df.query('Total Revenue > 100')
    • df.query('Total_Revenue > 100')
    • df.query('`Total Revenue` > 100')
    • df.query('[Total Revenue] > 100')

    Answer: df.query('`Total Revenue` > 100'). Option 3 is correct because backticks are the standard syntax for handling identifiers with spaces in query(). Option 1 causes a syntax error due to the space. Option 2 assumes a column name change. Option 4 is incorrect syntax for column referencing.

    From lesson: Conditional Selection with query()

  45. What happens if you run `df.query('col > 5')` on a DataFrame that does not contain 'col'?

    • It returns an empty DataFrame.
    • It raises an UndefinedVariableError.
    • It silently ignores the filter and returns the original DataFrame.
    • It creates the column with NaN values.

    Answer: It raises an UndefinedVariableError.. Option 2 is correct; query() raises an error if it cannot resolve a name as either a column or a local variable. Options 1, 3, and 4 are incorrect because Pandas does not guess or suppress these missing identifier errors.

    From lesson: Conditional Selection with query()

  46. Which of the following is the most efficient way to access data where you want to keep specific levels but slice across others in a MultiIndex?

    • Iterate through the rows using a loop and check index values
    • Use the .xs() method or pd.IndexSlice for cleaner syntax
    • Convert the index to a column using reset_index and filter with a boolean mask
    • Use loc with a list of all possible index combinations

    Answer: Use the .xs() method or pd.IndexSlice for cleaner syntax. The .xs() method and pd.IndexSlice are designed for multi-level indexing. Iterating is slow, resetting the index is unnecessary overhead, and loc with a full list is brittle.

    From lesson: MultiIndex and Hierarchical Indexing

  47. What happens if you perform a `.loc` slice on a non-sorted MultiIndex?

    • Pandas automatically sorts it for you
    • Pandas raises a KeyError
    • The operation succeeds but returns unpredictable results
    • The operation is significantly slower but otherwise identical

    Answer: Pandas raises a KeyError. Pandas requires the index to be lexsorted for efficient slicing. Attempting to slice a non-sorted MultiIndex typically results in a KeyError, whereas the other options are either incorrect or misleading regarding performance vs. functionality.

    From lesson: MultiIndex and Hierarchical Indexing

  48. When grouping by multiple levels of a MultiIndex, which is the most concise way to specify levels?

    • df.groupby(level=['Level1', 'Level2'])
    • df.groupby(['Level1', 'Level2'])
    • df.groupby(index=[0, 1])
    • df.groupby(levels=[0, 1])

    Answer: df.groupby(level=['Level1', 'Level2']). The level parameter in groupby accepts a list of level names or integers. Using names is more readable. The other options are syntactically invalid or incorrectly named.

    From lesson: MultiIndex and Hierarchical Indexing

  49. If you have a DataFrame with a MultiIndex and you execute `df.stack()`, what occurs?

    • The columns are compressed into a single level
    • The columns are pivoted into a new index level
    • The index is flattened into a single-level index
    • The index is sorted to optimize performance

    Answer: The columns are pivoted into a new index level. stack() pivots columns into the index, effectively increasing the depth of the MultiIndex. It does not flatten the index or sort it, and the other options describe the inverse or unrelated operations.

    From lesson: MultiIndex and Hierarchical Indexing

  50. Why would you choose to use a MultiIndex instead of simply having those categories as standard columns?

    • It consumes significantly less memory
    • It is mandatory for all DataFrames with more than two variables
    • It allows for hierarchical selection and automatic alignment during arithmetic
    • It makes the DataFrame easier to export to CSV files

    Answer: It allows for hierarchical selection and automatic alignment during arithmetic. MultiIndex is a powerful tool for hierarchical data organization, enabling complex slicing and automatic alignment. It does not necessarily reduce memory usage, is not mandatory, and actually complicates CSV storage.

    From lesson: MultiIndex and Hierarchical Indexing

  51. You have a DataFrame where you want to remove any row that contains at least one missing value. Which command is most appropriate?

    • df.dropna(how='all')
    • df.dropna(how='any')
    • df.dropna(inplace=False)
    • df.drop_duplicates()

    Answer: df.dropna(how='any'). how='any' is the default behavior that drops a row if any column is null. 'all' only drops rows if every column is null. 'inplace=False' is default and doesn't perform the drop on the current variable. 'drop_duplicates' removes duplicate rows, not missing data.

    From lesson: Handling Missing Data — isnull, dropna, fillna

  52. What is the result of applying df.isnull().sum() to a DataFrame?

    • A count of missing values per column
    • A boolean DataFrame indicating missing values
    • A total count of missing values in the entire DataFrame
    • The indices of all rows with missing values

    Answer: A count of missing values per column. isnull() creates a mask of True/False values. .sum() defaults to axis 0 (columns), resulting in the count of missing values per column. The other options describe different operations (boolean mask, aggregation total, or boolean indexing).

    From lesson: Handling Missing Data — isnull, dropna, fillna

  53. If you perform 'df.fillna(0)', what happens to the DataFrame?

    • All missing values in the entire DataFrame are replaced with 0
    • Only the first column's missing values are replaced with 0
    • Nothing happens because you did not specify axis=0
    • The original DataFrame is updated in place

    Answer: All missing values in the entire DataFrame are replaced with 0. Calling fillna(0) on a DataFrame applies the replacement to all missing values across all columns. It does not update the original DataFrame by default (it returns a copy), and it does not require an axis argument to operate globally.

    From lesson: Handling Missing Data — isnull, dropna, fillna

  54. Why might you prefer 'df.fillna(method='ffill')' over 'df.fillna(0)'?

    • To replace missing data with the most frequent value
    • To replace missing data with the previous non-null value
    • To fill missing values only in the first row
    • To drop all rows that contain missing values

    Answer: To replace missing data with the previous non-null value. ffill (forward fill) propagates the last valid observation forward to fill gaps. The other options describe different approaches like statistical imputation, simple constant filling, or row dropping.

    From lesson: Handling Missing Data — isnull, dropna, fillna

  55. Which of the following expressions correctly filters a DataFrame 'df' to keep only rows where the column 'price' is not missing?

    • df[df['price'].isnull()]
    • df.dropna(subset=['price'])
    • df.fillna(subset=['price'])
    • df[~df['price'].notnull()]

    Answer: df.dropna(subset=['price']). dropna(subset=['price']) specifically targets the 'price' column to remove rows where it is NaN. The isnull() option filters for missing values, not non-missing. fillna doesn't filter data, and the last option uses a double negative that incorrectly selects missing values.

    From lesson: Handling Missing Data — isnull, dropna, fillna

  56. Given a DataFrame 'df' with columns ['A', 'B'], which operation correctly removes rows where both 'A' and 'B' are duplicates, leaving the last occurrence?

    • df.drop_duplicates(subset=['A', 'B'], keep='last')
    • df.drop_duplicates(subset='A, B', keep='last')
    • df.drop_duplicates(keep='last', subset='all')
    • df.drop_duplicates(columns=['A', 'B'], keep='last')

    Answer: df.drop_duplicates(subset=['A', 'B'], keep='last'). Option 0 is correct because subset accepts a list of column names. Option 1 uses an invalid string format. Option 2 does not use the subset argument. Option 3 uses 'columns' instead of 'subset'.

    From lesson: Removing Duplicates

  57. If you perform 'df.drop_duplicates(subset='ID')' and your DataFrame has indices 0, 1, and 2, what happens to the indices of the resulting DataFrame?

    • The indices are automatically re-indexed to 0, 1, 2...
    • The original indices are preserved for the remaining rows.
    • The indices are removed entirely.
    • The indices are sorted alphabetically.

    Answer: The original indices are preserved for the remaining rows.. Pandas preserves the original index of the rows that are kept. Option 0 is wrong because re-indexing requires an explicit call. Option 2 and 3 are incorrect as indices persist.

    From lesson: Removing Duplicates

  58. Why might 'df.drop_duplicates()' fail to remove rows you believe are duplicates?

    • Because the values are actually different due to subtle floating-point precision differences.
    • Because the column order in the DataFrame is different.
    • Because drop_duplicates only works on numeric data types.
    • Because there are no integer types in the DataFrame.

    Answer: Because the values are actually different due to subtle floating-point precision differences.. Floating-point numbers can vary by tiny fractions, making them not 'equal'. Options 1, 2, and 3 are false because drop_duplicates works on various types and is independent of column order.

    From lesson: Removing Duplicates

  59. What is the primary difference between 'keep="first"' and 'keep=False' in drop_duplicates?

    • keep='first' removes all duplicates; keep=False keeps the first one.
    • keep='first' keeps one instance; keep=False drops every row that has any duplicate.
    • keep='first' is faster; keep=False is more memory intensive.
    • keep='first' requires sorting; keep=False does not.

    Answer: keep='first' keeps one instance; keep=False drops every row that has any duplicate.. keep='first' retains one instance of the duplicate group, while keep=False marks all rows that have duplicates as duplicates and drops all of them. The other options misstate the functionality.

    From lesson: Removing Duplicates

  60. If you want to remove duplicates but the method does not seem to change your original variable 'df', what is the most likely reason?

    • The DataFrame was already unique.
    • The function was called but not assigned back to a variable or used with inplace=True.
    • The data is too large for the computer's memory.
    • The columns contain null values.

    Answer: The function was called but not assigned back to a variable or used with inplace=True.. Pandas methods are non-mutating by default. Option 0 is possible but less likely to cause this specific confusion than the API design. Options 2 and 3 do not prevent the function from returning a value.

    From lesson: Removing Duplicates

  61. You have a column 'price' with values like '$10.50' and '$20.00'. Why will df['price'].astype('float') fail?

    • The dollar signs prevent pandas from parsing the strings as floating-point numbers.
    • Floats cannot represent currency values accurately due to precision issues.
    • The astype method only works on columns that are already numeric.
    • Pandas requires you to use the 'currency' data type instead of 'float'.

    Answer: The dollar signs prevent pandas from parsing the strings as floating-point numbers.. Option 1 is correct because '$' is a non-numeric character. Option 2 is incorrect because floats can represent currency, just not with decimal exactness. Option 3 is incorrect because astype can convert strings. Option 4 is incorrect as no such type exists.

    From lesson: Data Type Conversion (astype)

  62. Which of the following is the most memory-efficient way to convert a column with only 'Male' and 'Female' labels?

    • astype('string')
    • astype('category')
    • astype('object')
    • astype('bool')

    Answer: astype('category'). Option 2 is correct because the 'category' type stores integers internally, mapping them to labels, which is highly efficient for low-cardinality strings. 'String' and 'object' store full text, and 'bool' does not fit the data.

    From lesson: Data Type Conversion (astype)

  63. If column 'A' contains [1, 2, np.nan], why does df['A'].astype('int64') throw an error?

    • Because numpy does not allow arrays to be modified.
    • Because 'int64' does not have a representation for missing values (NaN).
    • Because 'A' must be converted to 'float' first.
    • Because the length of the list is not specified.

    Answer: Because 'int64' does not have a representation for missing values (NaN).. Option 2 is correct; 'int64' is a primitive type that does not support NaN. Option 1 is irrelevant. Option 3 is incorrect as converting to float won't solve the conversion to int. Option 4 is incorrect as length is handled automatically.

    From lesson: Data Type Conversion (astype)

  64. What happens when you use pd.to_numeric(series, errors='coerce') on a column with non-convertible text?

    • Pandas raises a ValueError and stops execution.
    • The non-convertible text is removed from the index.
    • The non-convertible values are replaced with NaN.
    • The method ignores the invalid values and leaves the original text.

    Answer: The non-convertible values are replaced with NaN.. Option 3 is the purpose of 'coerce', which treats errors as missing data. Option 1 describes the default behavior (errors='raise'). Option 2 is incorrect as it affects values, not indices. Option 4 describes errors='ignore'.

    From lesson: Data Type Conversion (astype)

  65. You want to convert a column of integers to strings. Which approach is preferred in idiomatic Pandas?

    • df['col'].astype(str)
    • df['col'].map(lambda x: str(x))
    • df['col'].apply(str)
    • All of the above are equally efficient.

    Answer: df['col'].astype(str). Option 1 is the most idiomatic and utilizes vectorized C-level operations. Options 2 and 3 involve Python-level loops, which are significantly slower for large datasets. Option 4 is incorrect because performance varies greatly.

    From lesson: Data Type Conversion (astype)

  66. You have a DataFrame 'df' and want to change column 'A' to 'Alpha'. Which command is correct?

    • df.rename(columns={'A': 'Alpha'}, inplace=True)
    • df.rename({'A': 'Alpha'}, axis='rows')
    • df.columns = ['Alpha']
    • df.reindex(columns={'A': 'Alpha'})

    Answer: df.rename(columns={'A': 'Alpha'}, inplace=True). Option 0 correctly uses a dictionary to map the old name to the new name and uses inplace to modify the object. Option 1 targets rows, not columns. Option 2 overwrites all columns with a single name, failing if the shape doesn't match. Option 3 is for reordering or changing index alignment, not renaming.

    From lesson: Renaming Columns and Index

  67. What happens if you run 'df.rename(index={0: 'First'})' without assigning it to a variable?

    • The index label 0 is permanently changed to 'First'.
    • The operation fails because you must provide a list of names.
    • The DataFrame remains unchanged because rename() returns a new object.
    • The operation raises an error because 'index' expects a function.

    Answer: The DataFrame remains unchanged because rename() returns a new object.. Pandas transformation methods return a copy unless 'inplace=True' is set; without assignment, the change is lost. Option 0 is false due to immutability. Option 1 is incorrect as dictionaries are standard. Option 3 is false as rename accepts labels or functions.

    From lesson: Renaming Columns and Index

  68. Which of the following approaches is most efficient for renaming all columns to lowercase?

    • Manually typing a dictionary of every column name.
    • Using df.rename(columns=str.lower).
    • Using df.index = df.index.str.lower().
    • Iterating through df.columns and renaming one by one.

    Answer: Using df.rename(columns=str.lower).. Passing a function like str.lower to the columns parameter applies the transformation to every column label automatically. Option 0 is tedious and error-prone. Option 2 renames the index, not columns. Option 3 is highly inefficient compared to vectorized renaming.

    From lesson: Renaming Columns and Index

  69. You want to rename index labels 101 and 102 to 'Start' and 'End'. How should you format the index argument?

    • index=[('Start', 'End')]
    • index={'101': 'Start', '102': 'End'}
    • index={101: 'Start', 102: 'End'}
    • index=['Start', 'End']

    Answer: index={101: 'Start', 102: 'End'}. The rename method expects a dictionary where keys match the current index labels (integers 101 and 102). Option 0 is a list of tuples, which is incorrect. Option 1 uses strings instead of the integer labels present. Option 3 replaces the whole index without mapping specific keys.

    From lesson: Renaming Columns and Index

  70. When using df.rename(columns={'old': 'new'}, axis=1), what is the effect of axis=1?

    • It triggers an error because 'columns' and 'axis=1' are redundant/conflicting.
    • It forces the rename to apply to the row index instead.
    • It acts as a synonym for 'columns', having no impact since 'columns' is already specified.
    • It reverses the renaming operation.

    Answer: It acts as a synonym for 'columns', having no impact since 'columns' is already specified.. Specifying 'columns' already defines the axis. Adding 'axis=1' is redundant but allowed by the API, resulting in no change to the behavior. Option 0 is wrong because Pandas handles redundant axis arguments. Option 1 is wrong because columns are specified. Option 3 is nonsense.

    From lesson: Renaming Columns and Index

  71. You have a Series of city names and want to rename 'NYC' to 'New York' and 'LA' to 'Los Angeles' while keeping other values unchanged. Which method is most efficient?

    • s.map({'NYC': 'New York', 'LA': 'Los Angeles'})
    • s.replace({'NYC': 'New York', 'LA': 'Los Angeles'})
    • s.apply(lambda x: 'New York' if x == 'NYC' else 'Los Angeles')
    • s.update({'NYC': 'New York', 'LA': 'Los Angeles'})

    Answer: s.replace({'NYC': 'New York', 'LA': 'Los Angeles'}). .replace() is designed to change specific values while leaving others alone. .map() would turn all values not in the dictionary into NaN, .apply() is unnecessarily verbose, and .update() modifies in-place and requires an index match, which is not the intended use here.

    From lesson: Replacing Values and Mapping

  72. What is the primary difference between .map() and .applymap() in Pandas?

    • .map() is for DataFrames, .applymap() is for Series
    • .map() performs element-wise operations, .applymap() performs row-wise operations
    • .map() is only for Series, .applymap() is only for DataFrames
    • .map() allows regex, .applymap() does not

    Answer: .map() is only for Series, .applymap() is only for DataFrames. .map() is a Series-only method for substituting values, while .applymap() is a DataFrame-only method that applies a function to every single element in the table. The other options misstate the object-method relationship or the function scope.

    From lesson: Replacing Values and Mapping

  73. If you perform s.map({'A': 1, 'B': 2}) on a Series containing ['A', 'B', 'C'], what is the result?

    • ['1', '2', 'C']
    • [1, 2, 2]
    • [1, 2, NaN]
    • Error: 'C' not found in mapping

    Answer: [1, 2, NaN]. .map() converts values found in the dictionary and converts any value not present in the dictionary keys to NaN. It does not return the original value for missing keys, nor does it raise an error.

    From lesson: Replacing Values and Mapping

  74. Which approach is correct to replace the string '?' with standard NaN values across a whole DataFrame?

    • df.replace('?', np.nan)
    • df.map('?', np.nan)
    • df.replace({'?': np.nan})
    • df.apply('?', np.nan)

    Answer: df.replace('?', np.nan). df.replace() works across the entire DataFrame to search for a value and replace it. While the third option works, the first is the standard, most idiomatic syntax. .map() and .apply() are not designed for broad multi-column value replacement in this manner.

    From lesson: Replacing Values and Mapping

  75. When using .replace(to_replace, value), what happens if 'to_replace' is a list and 'value' is a list?

    • It raises a ValueError
    • It maps the first item in the first list to the first item in the second list, and so on
    • It performs a Cartesian product replacement
    • It replaces all occurrences of all items in the first list with the entire second list

    Answer: It maps the first item in the first list to the first item in the second list, and so on. Pandas allows mapping multiple values to multiple replacements by passing corresponding lists of equal length. This is a positional mapping feature. It does not raise an error, nor does it create a Cartesian product or assign the full list as a single value.

    From lesson: Replacing Values and Mapping

  76. You have a DataFrame 'df' and want to apply a function to every single cell regardless of its type. Which method is most appropriate?

    • df.map(func)
    • df.apply(func)
    • df.applymap(func)
    • df.transform(func)

    Answer: df.applymap(func). applymap() is designed for element-wise operations on DataFrames. map() only works on Series, apply() is typically for axis-based reduction, and transform() is for broadcasting operations.

    From lesson: apply(), map(), and applymap()

  77. When using df.apply(my_func, axis=1), what is the input passed into 'my_func'?

    • A single value from the cell
    • A Pandas Series representing a row
    • A Pandas Series representing a column
    • The entire DataFrame

    Answer: A Pandas Series representing a row. Setting axis=1 tells Pandas to iterate over the DataFrame row by row. Each row is passed as a Series object to the custom function. The other options describe axis=0 behavior or general mapping.

    From lesson: apply(), map(), and applymap()

  78. Which of these is the most efficient way to convert all elements in a Series from Celsius to Fahrenheit?

    • series.apply(lambda x: x * 9/5 + 32)
    • series.map(lambda x: x * 9/5 + 32)
    • series * 9/5 + 32
    • series.applymap(lambda x: x * 9/5 + 32)

    Answer: series * 9/5 + 32. Vectorized operations (series * constant) are significantly faster than apply or map because they run in optimized C code rather than Python loops. applymap() does not exist for Series.

    From lesson: apply(), map(), and applymap()

  79. You want to replace values in a Series based on a dictionary (e.g., {'A': 1, 'B': 2}). Which method is designed specifically for this?

    • series.map(dict)
    • series.apply(dict)
    • series.applymap(dict)
    • series.transform(dict)

    Answer: series.map(dict). The map() method on a Series is optimized to accept a dictionary or another Series for substitution. apply() and applymap() are intended for function execution, not dictionary-based lookup.

    From lesson: apply(), map(), and applymap()

  80. What happens if you use df.apply(sum) on a DataFrame without specifying an axis?

    • It sums every element in the entire DataFrame into one scalar.
    • It sums each row individually.
    • It sums each column individually.
    • It raises a TypeError because sum is not an apply method.

    Answer: It sums each column individually.. The default axis for apply() is 0, which corresponds to columns. Thus, it performs the operation on each column. It does not sum all cells into one scalar, nor does it default to rows.

    From lesson: apply(), map(), and applymap()

  81. Which of the following describes the behavior of `df['names'].str.len()` if the column contains `None` values?

    • It raises a TypeError because None is not a string.
    • It returns 0 for all None values.
    • It returns NaN for rows containing None.
    • It ignores the rows with None and returns a shorter Series.

    Answer: It returns NaN for rows containing None.. Pandas propagates NaN values in vectorized string operations. Option 0 is wrong because Pandas handles None gracefully by converting it to NaN. Option 1 is incorrect because it does not assume length 0. Option 3 is wrong because the index is preserved, and the result remains the same length as the original Series.

    From lesson: String Operations with str accessor

  82. What is the result of `df['text'].str.contains('abc')` if the column contains `NaN` values?

    • The operation will result in True for those rows.
    • The operation will result in False for those rows.
    • The operation will result in NaN for those rows.
    • The operation will raise a ValueError.

    Answer: The operation will result in NaN for those rows.. Missing values are treated as missing in string searches, resulting in NaN. Options 0 and 1 are incorrect because they imply a boolean outcome. Option 3 is wrong as .str methods are designed to handle missing data through propagation.

    From lesson: String Operations with str accessor

  83. You have a column of strings and want to split them by a comma into two distinct columns. What is the most efficient way to achieve this?

    • Use .str.split(',', expand=True)
    • Use .str.split(',', expand=False)
    • Use .apply(lambda x: x.split(','))
    • Use .str.slice() to split at the comma position

    Answer: Use .str.split(',', expand=True). expand=True is specifically designed to transform the resulting list-like elements into a DataFrame. Option 1 results in a Series of lists, which is not the desired structure. Option 2 is inefficient compared to the vectorized .str method. Option 3 is brittle as it requires knowing the comma position in advance.

    From lesson: String Operations with str accessor

  84. If you need to replace all occurrences of a literal string '$' without it being interpreted as a regex special character, how should you call the replace method?

    • df['col'].str.replace('$', 'USD')
    • df['col'].str.replace('$', 'USD', regex=False)
    • df['col'].str.replace(r'$', 'USD')
    • df['col'].str.replace(literal='$', 'USD')

    Answer: df['col'].str.replace('$', 'USD', regex=False). By default, .str.replace() treats the pattern as a regular expression. Setting regex=False ensures '$' is treated as a literal character. Option 0 will fail or behave unexpectedly due to the regex meaning of '$'. Options 2 and 3 use incorrect syntax or do not resolve the regex issue.

    From lesson: String Operations with str accessor

  85. How do you combine a column of integers with a string suffix in Pandas?

    • df['col'] + '_suffix'
    • df['col'].astype(str) + '_suffix'
    • df['col'].str.cat('_suffix')
    • df['col'].add('_suffix')

    Answer: df['col'].astype(str) + '_suffix'. Pandas requires explicit type casting to string before concatenation. Option 0 will raise a TypeError due to mismatched types. Option 2 requires the input to already be a string-based Series. Option 3 is for numeric addition, not string concatenation.

    From lesson: String Operations with str accessor

  86. Which of the following is the most efficient way to extract only the year from a Series of datetime objects?

    • df['date'].dt.strftime('%Y')
    • df['date'].apply(lambda x: x.year)
    • df['date'].dt.year
    • df['date'].map(lambda x: x.strftime('%Y'))

    Answer: df['date'].dt.year. df['date'].dt.year is the most efficient because it uses vectorized access. The .apply() and .map() functions are slower as they process elements one by one, and strftime() converts values to strings, losing the numeric type.

    From lesson: DateTime Handling with dt accessor

  87. If you have a Series of timestamps and need to perform time-series arithmetic, why is using .dt.normalize() preferred over .dt.date?

    • normalize() is faster because it works in-place.
    • normalize() returns a datetime64 Series, while .dt.date returns a series of objects.
    • normalize() removes time zones, while .dt.date keeps them.
    • normalize() is the only way to get the date component.

    Answer: normalize() returns a datetime64 Series, while .dt.date returns a series of objects.. .dt.normalize() keeps the data in a pandas-native datetime64 format, allowing for continued vectorization. .dt.date converts the data to Python 'date' objects, which effectively breaks the pandas performance optimization for further analysis.

    From lesson: DateTime Handling with dt accessor

  88. What is the primary difference between .dt.dayofweek and .dt.day_name()?

    • dayofweek is zero-indexed integers, while day_name returns strings.
    • dayofweek returns human-readable names, while day_name returns integers.
    • dayofweek is for pandas versions > 2.0, while day_name is legacy.
    • They both return the same type of data but with different performance.

    Answer: dayofweek is zero-indexed integers, while day_name returns strings.. .dt.dayofweek returns integers (Monday=0 to Sunday=6), which is ideal for mathematical modeling. .dt.day_name() returns strings (e.g., 'Monday'), which is useful for visualization but less useful for numeric computation.

    From lesson: DateTime Handling with dt accessor

  89. After applying pd.to_datetime() to a column, what happens if you attempt to use .dt on a value that failed to parse (resulting in NaT)?

    • The entire Series will become a string object.
    • The operation will raise a TypeError immediately.
    • The operation ignores the NaT and processes the valid timestamps.
    • The operation will return 0 for all NaT entries.

    Answer: The operation ignores the NaT and processes the valid timestamps.. Pandas handles missing values (NaT) gracefully within .dt operations. Valid entries will be processed, while NaT entries will simply return NaN or NaT in the output, preventing code crashes.

    From lesson: DateTime Handling with dt accessor

  90. You want to filter a DataFrame for all entries occurring on a weekend. Which approach is most idiomatic?

    • df[df['date'].dt.day_name().isin(['Saturday', 'Sunday'])]
    • df[df['date'].dt.dayofweek >= 5]
    • df[df['date'].apply(lambda x: x.weekday() > 4)]
    • df[df['date'].dt.dayofweek > 5]

    Answer: df[df['date'].dt.dayofweek >= 5]. Checking if .dt.dayofweek >= 5 is the most performant and robust way to identify weekends. Option 0 relies on locale-specific strings, option 2 is inefficient due to apply(), and option 3 would miss Saturdays (index 5).

    From lesson: DateTime Handling with dt accessor

  91. Which parameter must be set to True if you want to modify your DataFrame directly without returning a new object?

    • inplace
    • axis
    • ascending
    • ignore_index

    Answer: inplace. The 'inplace' parameter, when set to True, modifies the existing object. 'axis' determines the direction of the sort, 'ascending' controls the order (high/low), and 'ignore_index' resets the index, but none of these perform an in-place mutation.

    From lesson: Sorting — sort_values and sort_index

  92. If you have a DataFrame and want to order the columns alphabetically, what is the correct approach?

    • df.sort_values(axis=1)
    • df.sort_index(axis=1)
    • df.sort_values(by=df.columns)
    • df.sort_index(axis=0)

    Answer: df.sort_index(axis=1). sort_index(axis=1) sorts the column labels. sort_values sorts by row values. sort_index(axis=0) sorts by row labels (index), and sorting by column names as a 'by' value is not the standard way to reorder columns.

    From lesson: Sorting — sort_values and sort_index

  93. How does Pandas handle missing values (NaN) during a sort_values operation by default?

    • It raises a ValueError.
    • It treats them as zero.
    • It puts them at the end of the sorted output.
    • It puts them at the beginning of the sorted output.

    Answer: It puts them at the end of the sorted output.. Pandas defaults 'na_position' to 'last', placing NaNs at the end. It does not raise an error or treat them as zero by default, and they are not at the beginning unless explicitly specified.

    From lesson: Sorting — sort_values and sort_index

  94. When sorting a MultiIndex DataFrame by the second level of the index, which parameter should be utilized?

    • by
    • axis
    • level
    • sort_remaining

    Answer: level. The 'level' parameter allows you to target specific levels of a MultiIndex. 'by' is for column values, 'axis' for orientation, and 'sort_remaining' is a boolean flag for cascading sorts, not for targeting a specific level.

    From lesson: Sorting — sort_values and sort_index

  95. If you run df.sort_values(by='score', ascending=False), what is the resulting order of data?

    • Highest score to lowest score.
    • Lowest score to highest score.
    • Alphabetical order of the 'score' column.
    • The DataFrame remains unchanged.

    Answer: Highest score to lowest score.. Setting 'ascending=False' flips the sort order to descending, meaning highest values appear first. Lowest to highest is ascending=True. Alphabetical sorting only occurs if the column contains strings, and the DataFrame is definitely changed.

    From lesson: Sorting — sort_values and sort_index

  96. You have a list of sales figures [10, 20, 20, 30]. If you apply rank(method='min'), what is the resulting rank for the value 20?

    • 1.5
    • 2
    • 3
    • 2.5

    Answer: 2. With method='min', tied values get the lowest rank in the group. Here, the values 20 occupy ranks 2 and 3, so both receive 2. Option 0 is the default 'average' rank. Option 2 is 'max' rank, and Option 3 is incorrect because method='min' returns an integer equivalent of the start of the tie.

    From lesson: Ranking and Cumulative Functions

  97. Which combination of parameters allows you to rank values such that the highest value gets rank 1?

    • ascending=True
    • ascending=False
    • method='dense'
    • pct=True

    Answer: ascending=False. Setting ascending=False ranks data in descending order, meaning the largest value is assigned rank 1. Ascending=True is the default (smallest is 1). Dense just controls how ties are handled, and pct=True returns percentages rather than ranks.

    From lesson: Ranking and Cumulative Functions

  98. If a column contains [10, NaN, 20], what is the result of cumsum()?

    • [10, 0, 30]
    • [10, NaN, 30]
    • [10, NaN, 20]
    • [10, 10, 30]

    Answer: [10, NaN, 30]. Pandas cumsum() skips NaNs by default, leaving them as NaN in the result. Option 0 assumes NaN is zero. Option 2 forgets to add 10 to 20. Option 3 is impossible as it implies forward-filling.

    From lesson: Ranking and Cumulative Functions

  99. What is the primary difference between 'dense' ranking and 'min' ranking?

    • Dense ranking skips no ranks, while min ranking creates gaps.
    • Min ranking is for floats, dense is for integers.
    • Dense ranking ignores ties, while min ranking counts them.
    • There is no difference in Pandas.

    Answer: Dense ranking skips no ranks, while min ranking creates gaps.. In dense ranking, if two items tie for 2nd place, the next item is 3rd. In min ranking, if two items tie for 2nd place, the next item is 4th (a gap is left). The other options incorrectly describe the behavior of these parameters.

    From lesson: Ranking and Cumulative Functions

  100. To calculate the cumulative product of returns (where returns are expressed as 1 + r), you use cumprod(). If you have data [1.1, 1.2], what does this represent?

    • The simple sum of returns.
    • The compounded growth over two periods.
    • The average performance per period.
    • The volatility of the returns.

    Answer: The compounded growth over two periods.. Cumulative product (cumprod) calculates compounding effects. Multiplying 1.1 * 1.2 represents the total growth after two periods. Summation (Option 0) is incorrect for compounding, and the other options describe completely different statistical measures.

    From lesson: Ranking and Cumulative Functions

  101. If you group a DataFrame by a column and then call .sum(), what happens to the grouping column?

    • It is removed from the DataFrame entirely.
    • It becomes the index of the resulting DataFrame by default.
    • It remains a standard column in the DataFrame.
    • It is transformed into a set of binary dummy variables.

    Answer: It becomes the index of the resulting DataFrame by default.. By default, as_index=True, so the grouping column becomes the index. Option 1 is wrong because the data is preserved; Option 3 is wrong because as_index=False is required for that; Option 4 is incorrect as grouping doesn't perform one-hot encoding.

    From lesson: GroupBy — split-apply-combine

  102. What is the primary difference between .aggregate() and .transform()?

    • Aggregate reduces the data to group-level summaries, while transform keeps the original shape.
    • Transform works only on numeric data, while aggregate works on any data type.
    • Aggregate is faster than transform on large datasets.
    • Transform cannot handle user-defined functions, while aggregate can.

    Answer: Aggregate reduces the data to group-level summaries, while transform keeps the original shape.. Transform broadcasts the group result to match the original index size, whereas aggregate returns one row per group. The other options are incorrect as both support user-defined functions and handle various data types.

    From lesson: GroupBy — split-apply-combine

  103. When using .filter() on a GroupBy object, what condition is required?

    • The filter function must return a scalar value.
    • The filter function must return a DataFrame with the same columns as the original.
    • The filter function must return a boolean value indicating if the whole group should be kept.
    • The filter function must return the original index of the rows.

    Answer: The filter function must return a boolean value indicating if the whole group should be kept.. The filter method evaluates a function on each group and expects a boolean; if True, the entire group is kept. Other options are incorrect because the function doesn't return indices or scalars, and it filters by group, not by specific DataFrame columns.

    From lesson: GroupBy — split-apply-combine

  104. Why might you use .apply() instead of .agg() in a split-apply-combine workflow?

    • Because .apply() is always faster than .agg().
    • Because .apply() can handle complex logic that returns arbitrary objects or sub-DataFrames.
    • Because .apply() automatically excludes non-numeric columns without extra arguments.
    • Because .agg() cannot be used with multiple columns simultaneously.

    Answer: Because .apply() can handle complex logic that returns arbitrary objects or sub-DataFrames.. .apply() is more flexible, allowing custom structures, while .agg() is optimized for standard aggregations. The other options are false because .agg() supports multiple columns and .apply() is generally slower than optimized .agg() functions.

    From lesson: GroupBy — split-apply-combine

  105. You have a DataFrame with 'Category' and 'Sales'. You want to calculate the mean of 'Sales' while keeping 'Category' as a column. How do you do it?

    • df.groupby('Category').mean(as_index=False)
    • df.groupby('Category', as_index=False)['Sales'].mean()
    • df.groupby('Category')['Sales'].mean().reset_index()
    • Both 2 and 3 are valid ways to achieve this.

    Answer: Both 2 and 3 are valid ways to achieve this.. Both using as_index=False during the groupby and calling .reset_index() after grouping are standard, effective patterns to keep the grouping key as a column. Option 1 is syntactically incorrect as as_index is a parameter of groupby, not mean.

    From lesson: GroupBy — split-apply-combine

  106. What is the primary difference in behavior between df.groupby('col').agg('mean') and df.groupby('col').transform('mean')?

    • agg() returns a reduced index based on unique groups, while transform() returns an index matching the original length.
    • agg() only works on numeric data, while transform() works on all data types.
    • transform() is significantly faster than agg() for large datasets.
    • agg() returns a Series, while transform() always returns a DataFrame.

    Answer: agg() returns a reduced index based on unique groups, while transform() returns an index matching the original length.. agg() reduces the number of rows to the number of unique groups, whereas transform() broadcasts the result back to the original index. The other options are incorrect because transform can also be limited by data types, performance depends on the operation, and both can return Series or DataFrames.

    From lesson: Aggregation Functions — agg, transform

  107. If you need to subtract the group mean from every individual record in the group, which method is most idiomatic?

    • df.groupby('category')['value'].agg(lambda x: x - x.mean())
    • df.groupby('category')['value'].transform(lambda x: x - x.mean())
    • df.groupby('category')['value'].apply(lambda x: x - x.mean())
    • df.groupby('category')['value'].map(lambda x: x - x.mean())

    Answer: df.groupby('category')['value'].transform(lambda x: x - x.mean()). transform() is designed to return an object indexed the same as the original, making it perfect for broadcasted calculations. agg() would fail to align the result, apply() is less efficient for this specific case, and map() is not a groupby method.

    From lesson: Aggregation Functions — agg, transform

  108. When using df.agg(['sum', 'mean']), what is the structure of the resulting object?

    • A 1D Series containing both sum and mean concatenated.
    • A DataFrame with the original columns as index and sum/mean as columns.
    • A DataFrame with the original columns as columns and sum/mean as the index.
    • A multi-index Series.

    Answer: A DataFrame with the original columns as columns and sum/mean as the index.. When passing a list of functions to agg(), Pandas returns a DataFrame where the index represents the functions applied and the columns match the input DataFrame columns. Options 1, 2, and 4 do not accurately describe the standard Pandas agg() output structure.

    From lesson: Aggregation Functions — agg, transform

  109. Why would you prefer using a named dictionary in agg(), such as {'col1': 'sum', 'col2': 'mean'}?

    • It is the only way to perform aggregations in Pandas.
    • It allows applying different aggregation functions to specific columns simultaneously.
    • It prevents the DataFrame from casting numeric values to floats.
    • It automatically renames the columns to avoid collisions.

    Answer: It allows applying different aggregation functions to specific columns simultaneously.. The dictionary syntax provides granular control, allowing you to specify exactly which function applies to which column. It is not the only method, it doesn't prevent type casting, and it doesn't automatically handle name collisions.

    From lesson: Aggregation Functions — agg, transform

  110. Which of the following functions passed to transform() would trigger an error if the group contains mixed types?

    • lambda x: x.fillna(0)
    • lambda x: x.sum()
    • lambda x: x.count()
    • lambda x: x.size

    Answer: lambda x: x.sum(). x.sum() attempts to add all elements in the group; if there are incompatible types like strings and integers, it will fail. fillna, count, and size are polymorphic or structural and generally handle mixed types gracefully in this context.

    From lesson: Aggregation Functions — agg, transform

  111. Which of the following scenarios best justifies the use of pivot_table over pivot?

    • When the index contains only unique values.
    • When you need to perform an aggregation on duplicate entries for a group.
    • When you want to display the raw data without any computation.
    • When you are working with a very small dataset that fits in memory.

    Answer: When you need to perform an aggregation on duplicate entries for a group.. pivot_table is designed to aggregate data when multiple values exist for the same row/column intersection. pivot raises a ValueError if duplicates exist. The other options do not distinguish between the two functions.

    From lesson: Pivot Tables and crosstab

  112. What happens if you run pd.crosstab(df['A'], df['B']) on a dataframe where 'A' and 'B' contain missing values?

    • The missing values are included as their own category by default.
    • The operation fails and throws a KeyError.
    • The missing values are automatically dropped from the calculation.
    • The function treats NaN as 0.

    Answer: The missing values are included as their own category by default.. By default, crosstab includes NaN values in the resulting table as a separate index/column label. The other options are incorrect because the function does not drop them by default, nor does it throw an error or cast them to zero.

    From lesson: Pivot Tables and crosstab

  113. If you perform a pivot_table and notice the result contains 'NaN' values in cells where no data existed, how do you change those to zero?

    • Set the fill_value parameter to 0.
    • Use the dropna=True argument.
    • Apply the .fillna(0) method directly to the dataframe after the pivot.
    • Both the first and third options are valid ways to achieve this.

    Answer: Both the first and third options are valid ways to achieve this.. Both specifying fill_value inside the pivot_table function and chaining .fillna(0) after the computation are correct and standard practices. Option 2 would remove those rows/cols instead of replacing the NaN.

    From lesson: Pivot Tables and crosstab

  114. When using pd.crosstab to compare two categorical variables, what does setting normalize='index' do?

    • It scales the data to have a mean of 0 and a standard deviation of 1.
    • It divides each cell value by the total count of the row to show proportions.
    • It replaces all frequencies with the column average.
    • It converts the table into a correlation matrix.

    Answer: It divides each cell value by the total count of the row to show proportions.. Normalization by index calculates row-wise percentages, where each row sums to 1. This is useful for comparing distributions across rows. The other options describe statistical transformations not performed by the normalize parameter.

    From lesson: Pivot Tables and crosstab

  115. Consider a table created with pivot_table. What is the most likely structure of the index if you pass index=['Category', 'Subcategory']?

    • A single-level index where categories are concatenated.
    • A MultiIndex (hierarchical index).
    • The dataframe is flattened into a long format.
    • The columns are swapped with the index levels.

    Answer: A MultiIndex (hierarchical index).. Providing a list of columns to the index argument in pandas creates a MultiIndex object, which allows for nested grouping. The other options describe different, incorrect, or reversed data structures.

    From lesson: Pivot Tables and crosstab

  116. You have a daily sales dataset and need to calculate a 7-day moving average, but you want a result for every day, starting from the first day, even if only 3 days of data are available. How do you configure this?

    • Use rolling(window=7, min_periods=1)
    • Use rolling(window=7, center=True)
    • Use expanding(window=7)
    • Use rolling(window=7, min_periods=7)

    Answer: Use rolling(window=7, min_periods=1). min_periods=1 allows the calculation to start immediately. Option 1 is correct because it relaxes the constraint. Option 2 centers the window but does not solve the initial period issue. Option 3 creates an expanding window, not a 7-day window. Option 4 requires a full 7 days before returning a value.

    From lesson: Rolling and Expanding Windows

  117. What is the primary difference between .rolling(window=5) and .expanding()?

    • Rolling uses a fixed window size; expanding includes all data from the start of the series to the current row.
    • Rolling is for integers; expanding is for datetime data.
    • Expanding is always faster because it uses less memory.
    • Rolling requires an explicit min_periods; expanding does not.

    Answer: Rolling uses a fixed window size; expanding includes all data from the start of the series to the current row.. The fundamental definition of rolling is a fixed-size window moving over the data, while expanding captures the cumulative history. Option 1 describes this correctly. Option 2 is false as both handle various types. Option 3 is incorrect as expanding requires accumulating more data. Option 4 is false as both can use min_periods.

    From lesson: Rolling and Expanding Windows

  118. When applying .rolling(window='3D') on a time series, what does the window represent?

    • Three data points regardless of time.
    • A temporal window spanning exactly 3 days.
    • Three distinct observations occurring on the same day.
    • A window that shifts by 3 days at a time.

    Answer: A temporal window spanning exactly 3 days.. Time-based windows (using offset strings) define the temporal range. Option 1 is wrong because it describes a integer window. Option 2 correctly identifies the time-based functionality. Option 3 is irrelevant to the time index logic. Option 4 is incorrect as the window shifts by the index frequency, not by the window size.

    From lesson: Rolling and Expanding Windows

  119. If you perform df.rolling(window=3).sum(), what happens to the first two rows of the resulting Series?

    • They contain the sum of the first two rows.
    • They are calculated as 0.
    • They are set to NaN.
    • They are dropped from the DataFrame.

    Answer: They are set to NaN.. Pandas requires enough data to fill the window (3). Without min_periods specified, the default behavior is to return NaN for positions where the window size is not fully met. Option 1 is wrong as it doesn't meet the window size. Option 2 is a common misconception. Option 4 is wrong as the dimensions remain the same.

    From lesson: Rolling and Expanding Windows

  120. Why might you use the center=True parameter in a rolling window calculation?

    • To perform the calculation on the last element of the window.
    • To align the window result with the middle of the window range instead of the right edge.
    • To increase the speed of the computation.
    • To ensure the window is calculated only on the middle 50% of the data.

    Answer: To align the window result with the middle of the window range instead of the right edge.. By default, labels align with the right-edge of the window. Setting center=True aligns the labels to the center of the window. Option 1 is the default behavior. Option 3 is unrelated to performance. Option 4 is a misunderstanding of what 'center' does in this context.

    From lesson: Rolling and Expanding Windows

  121. If you have a DataFrame with daily data and you perform .resample('M').sum(), what happens to the resulting index?

    • The index becomes the first day of every month
    • The index becomes the last calendar day of every month
    • The index remains the original daily timestamps
    • The index is converted to a simple integer range

    Answer: The index becomes the last calendar day of every month. By default, 'M' (MonthEnd) frequency labels the bins by the last day of the month. Option 0 is incorrect because 'MS' (MonthStart) would be needed for that. Option 2 is wrong because resampling aggregates rows. Option 3 is wrong because index labels represent the bin intervals.

    From lesson: Resample for Time Series

  122. What is the primary difference between .resample() and .groupby() when dealing with time-based data?

    • Resample is faster for non-time-series data
    • Groupby does not require a datetime index while resample does
    • Resample is only for downsampling, while groupby is only for upsampling
    • Groupby automatically fills missing time intervals

    Answer: Groupby does not require a datetime index while resample does. Resample is a specialized form of groupby that explicitly handles datetime index requirements. Option 0 is wrong because resample is specifically for time. Option 2 is wrong because both handle upsampling and downsampling. Option 3 is wrong because resample (not groupby) is the one that facilitates filling gaps via .asfreq() or .interpolate().

    From lesson: Resample for Time Series

  123. You have a Series with a DatetimeIndex and some missing hours. You perform .resample('H').mean(). What is the impact of the missing hours?

    • Pandas ignores the missing hours entirely
    • Pandas throws a KeyError
    • Pandas creates new index entries for the missing hours with NaN values
    • Pandas automatically fills the gaps with the mean of the entire dataset

    Answer: Pandas creates new index entries for the missing hours with NaN values. Resample expands the index to match the frequency, inserting NaN values where no data exists in the source. Option 0 is wrong because the index is expanded. Option 1 is wrong because this is a standard operation. Option 3 is wrong because aggregation methods do not impute data unless specified.

    From lesson: Resample for Time Series

  124. How can you prevent Pandas from including the right bin edge in your resampling results?

    • Set closed='left'
    • Set closed='right'
    • Use .asfreq() instead of an aggregation method
    • Set label='left'

    Answer: Set closed='left'. Setting closed='left' means the intervals are [left, right), excluding the right edge. Option 1 is the default for many frequencies. Option 2 is a selection method, not an interval boundary parameter. Option 3 labels the bins, it does not change the interval inclusion.

    From lesson: Resample for Time Series

  125. When upsampling from daily to hourly data using .resample('H'), why might you see NaNs in your result?

    • The dataset has become too large to process
    • Upsampling increases the number of rows, leaving empty slots between the original daily timestamps
    • The datetime index is corrupted
    • Upsampling requires a numerical index, not a datetime index

    Answer: Upsampling increases the number of rows, leaving empty slots between the original daily timestamps. Upsampling creates rows for the new higher frequency; since there is no source data for these new intermediate timestamps, they become NaNs. Option 0 is incorrect as it is a standard behavior. Option 2 is irrelevant to the existence of NaNs. Option 3 is false as resample requires a datetime index.

    From lesson: Resample for Time Series

  126. You have two DataFrames with completely different column names. If you perform pd.concat([df1, df2], axis=0), what is the shape of the result?

    • A DataFrame with all columns from both, filled with NaNs where data is missing.
    • An error, because column names must match for vertical concatenation.
    • A DataFrame containing only the overlapping columns.
    • A DataFrame with columns from df1 only, discarding df2 data.

    Answer: A DataFrame with all columns from both, filled with NaNs where data is missing.. By default, concat uses an outer join, which unions the columns. Options 1 and 3 are incorrect because inner join is not the default. Option 2 is incorrect because vertical stacking doesn't require matching column names.

    From lesson: concat — Stacking DataFrames

  127. What is the primary difference between setting ignore_index=True versus ignore_index=False in pd.concat()?

    • True removes all rows with duplicate values, False keeps them.
    • True forces the index to be 0 to n-1, False preserves the original indices.
    • True merges indices, False deletes them entirely.
    • True improves speed by skipping index alignment, False performs expensive index sorting.

    Answer: True forces the index to be 0 to n-1, False preserves the original indices.. ignore_index=True creates a new range index (0, 1, 2...). Option 0 is wrong because concat doesn't perform deduplication; Option 2 is false as it doesn't delete indices; Option 3 is wrong because index alignment is inherent.

    From lesson: concat — Stacking DataFrames

  128. When concatenating DataFrames horizontally (axis=1), how does Pandas determine which rows align?

    • It aligns by the numerical position of the row (row 0 with row 0).
    • It aligns by the index label of the rows.
    • It concatenates based on the order of insertion regardless of index.
    • It aligns based on the values in the first column.

    Answer: It aligns by the index label of the rows.. Pandas aligns data based on index labels. Options 0 and 2 are wrong because Pandas respects indices, not positions. Option 3 is irrelevant to Pandas' internal alignment logic.

    From lesson: concat — Stacking DataFrames

  129. If you want to concatenate a list of 100 DataFrames, why is it better to use pd.concat(list_of_dfs) instead of looping through and calling concat in each iteration?

    • pd.concat() automatically handles the join type when passed a list.
    • Calling concat inside a loop creates excessive memory overhead due to repeated DataFrame copying.
    • pd.concat() with a list is the only way to ensure index stability.
    • The list method is required to use axis=1 correctly.

    Answer: Calling concat inside a loop creates excessive memory overhead due to repeated DataFrame copying.. Concatenation creates a new object; repeating this in a loop causes quadratic performance degradation. The other options are incorrect because they don't explain the performance benefit of list-based construction.

    From lesson: concat — Stacking DataFrames

  130. What happens if you use join='inner' during a vertical concatenation (axis=0) of two DataFrames with different column sets?

    • It raises a ValueError.
    • It returns a DataFrame containing only the columns common to both input DataFrames.
    • It returns a DataFrame containing all columns from both, replacing missing values with 0.
    • It performs an outer join regardless of the parameter setting.

    Answer: It returns a DataFrame containing only the columns common to both input DataFrames.. An inner join keeps only the intersection of columns. Option 0 is wrong because it's a valid operation. Option 2 is wrong because inner join doesn't fill NaNs, it removes them. Option 3 is logically false.

    From lesson: concat — Stacking DataFrames

  131. You have two DataFrames, df1 and df2. df1 has 100 rows and df2 has 50 rows. You perform a merge using 'how=left'. The result has 120 rows. What does this imply?

    • The merge was performed incorrectly.
    • The join key in df2 contains duplicate values for at least some keys present in df1.
    • The merge was actually an 'outer' join.
    • There are missing values in the join key of df1.

    Answer: The join key in df2 contains duplicate values for at least some keys present in df1.. In a left join, if the right table has multiple rows with the same join key, Pandas replicates the row from the left table for each match. Option 1 is incorrect because this is standard behavior. Option 3 is incorrect as the row count would be different. Option 4 would not increase the row count.

    From lesson: merge — SQL-style Joins

  132. Which parameter allows you to merge on the DataFrame index instead of a specific column?

    • on='index'
    • join_index=True
    • left_index=True and right_index=True
    • merge_on='index'

    Answer: left_index=True and right_index=True. Pandas uses left_index=True and right_index=True to indicate that the index should be used as the merge key. Options 1, 2, and 4 are not valid parameters for the merge function.

    From lesson: merge — SQL-style Joins

  133. What happens if you perform an 'inner' merge on two DataFrames that share no common keys?

    • An error is raised.
    • It returns the left DataFrame.
    • It returns an empty DataFrame.
    • It returns a DataFrame containing all rows with NaN values.

    Answer: It returns an empty DataFrame.. An inner join only returns the intersection of the keys. If there is no overlap, the intersection is empty. Option 1 is false because it is a valid operation. Option 4 is characteristic of an outer join, not inner.

    From lesson: merge — SQL-style Joins

  134. If you merge two DataFrames and want to prevent duplicate columns by keeping specific values from only one side, which approach is best?

    • Drop the columns manually after the merge.
    • Use the 'suffixes' parameter to rename one side to an empty string.
    • Perform the merge and then use concat.
    • Only select the needed columns from the right DataFrame before merging.

    Answer: Only select the needed columns from the right DataFrame before merging.. Selecting only the subset of columns needed from the right DataFrame before merging is the cleanest and most memory-efficient way to avoid column overlap. Using suffixes (Option 2) is often not supported with empty strings, and manual dropping (Option 1) is less efficient.

    From lesson: merge — SQL-style Joins

  135. Why would you choose merge() over join() when working with DataFrames?

    • merge() is faster for all operations.
    • join() can only join on indices, while merge() allows joining on arbitrary columns.
    • join() does not support 'left' joins.
    • merge() requires both DataFrames to have the same index.

    Answer: join() can only join on indices, while merge() allows joining on arbitrary columns.. join() is primarily designed for index-on-index merges, whereas merge() provides much more flexibility by allowing joins on any combination of columns or indices. Option 3 is false as join() defaults to left, and Option 4 is incorrect as join() is the one constrained by indices.

    From lesson: merge — SQL-style Joins

  136. What is the primary difference between 'df.join(other)' and 'df.merge(other)'?

    • join defaults to index-based merging, while merge defaults to column-based merging.
    • join only works on Series objects, while merge works on DataFrames.
    • merge is faster than join for all datasets.
    • join is only available for outer joins, while merge handles all join types.

    Answer: join defaults to index-based merging, while merge defaults to column-based merging.. join is a convenience method for index-on-index merging. merge is more flexible and intended for column-based joins. Option 1 is wrong because join handles DataFrames. Option 2 is wrong because merge is not inherently faster. Option 3 is wrong because join supports 'inner', 'outer', and 'left' joins.

    From lesson: join — Index-based Merging

  137. You have two DataFrames, A and B. Both have a column named 'ID'. You want to join them on this column. Which approach is most idiomatic using 'join'?

    • A.join(B, on='ID')
    • A.join(B.set_index('ID'), on='ID')
    • A.set_index('ID').join(B.set_index('ID'))
    • A.join(B, left_on='ID', right_on='ID')

    Answer: A.set_index('ID').join(B.set_index('ID')). join is index-based, so you must set the join key as the index for both DataFrames first. Option 0 and 3 are syntax errors or incorrect usage. Option 1 is redundant/misused syntax.

    From lesson: join — Index-based Merging

  138. Why would a join operation result in columns with '_x' and '_y' suffixes?

    • The join was performed using the outer method.
    • The DataFrames were joined on a non-indexed column.
    • The DataFrames share overlapping column names that were not explicitly handled.
    • The indices contained duplicate values.

    Answer: The DataFrames share overlapping column names that were not explicitly handled.. Pandas adds suffixes to prevent column name collisions during merges/joins. Option 0 is wrong because the type of join doesn't cause this; column names do. Options 1 and 3 are unrelated to suffix generation.

    From lesson: join — Index-based Merging

  139. If you perform 'left_df.join(right_df, how="inner")', what is the result?

    • A union of indices from both DataFrames.
    • Only rows where the index exists in both DataFrames.
    • All rows from the left DataFrame, filling missing right values with NaN.
    • All rows from the right DataFrame, filling missing left values with NaN.

    Answer: Only rows where the index exists in both DataFrames.. An 'inner' join keeps only the intersection of indices. Option 0 describes an outer join. Option 2 describes a left join. Option 3 describes a right join.

    From lesson: join — Index-based Merging

  140. What happens if the index of the DataFrame being joined to is not unique?

    • Pandas raises a KeyError.
    • Pandas automatically drops the duplicate index entries.
    • Pandas performs a Cartesian product for the matching indices, potentially increasing row count.
    • The operation is aborted to preserve data integrity.

    Answer: Pandas performs a Cartesian product for the matching indices, potentially increasing row count.. Joining on a non-unique index results in a Cartesian product for matching keys, duplicating rows to account for all combinations. Options 0, 1, and 3 are incorrect as Pandas allows and processes joins with non-unique indices.

    From lesson: join — Index-based Merging

  141. You have a column with 1 million rows and only 5 unique string values. What is the most effective way to reduce its memory footprint?

    • Convert the column to the 'string' dtype
    • Convert the column to the 'category' dtype
    • Keep it as the 'object' dtype
    • Convert the column to the 'float32' dtype

    Answer: Convert the column to the 'category' dtype. Converting to 'category' replaces unique strings with integers, significantly reducing memory for low-cardinality data. 'String' dtype is slightly better than 'object' but not as efficient as 'category' for repeating values. 'Object' is the default and is memory-heavy. 'Float32' is not suitable for string data.

    From lesson: Memory Optimization with dtypes

  142. Why does downcasting a float64 column to float32 result in lower memory usage?

    • Because it reduces the number of rows in the dataframe
    • Because it reduces the number of bytes used to store each floating-point value
    • Because it removes all missing values from the column
    • Because it converts the data into categorical integers

    Answer: Because it reduces the number of bytes used to store each floating-point value. Float64 uses 8 bytes per value, while float32 uses 4 bytes per value. Reducing the byte size per element halves the memory footprint. It does not change row count, remove missing values, or alter data type to categories.

    From lesson: Memory Optimization with dtypes

  143. Which of the following scenarios is the best use case for int8?

    • A column representing years (e.g., 2020, 2021, 2022)
    • A column representing a percentage from 0 to 100
    • A column representing customer IDs reaching 500,000
    • A column representing currency values up to $1,000,000

    Answer: A column representing a percentage from 0 to 100. Int8 can store values between -128 and 127. Percentages from 0 to 100 fit perfectly. Years are too large for int8, and IDs and currency values far exceed the limit.

    From lesson: Memory Optimization with dtypes

  144. When using pd.to_numeric(df['col'], downcast='integer'), what is Pandas actually doing?

    • It forces the data to be a string to avoid overflow
    • It sets all values to zero to save maximum space
    • It automatically finds the smallest integer dtype that can hold all values in the column
    • It converts integers to floats for higher precision

    Answer: It automatically finds the smallest integer dtype that can hold all values in the column. The downcast argument instructs Pandas to scan the data and find the most compact integer type (like int8, int16, etc.) that safely accommodates all current values. It does not force strings, zero out data, or convert to floats.

    From lesson: Memory Optimization with dtypes

  145. A column contains only True and False values but is currently an 'object' type. What is the impact of converting it to 'bool'?

    • The memory usage will increase because boolean objects are larger
    • The memory usage will stay exactly the same
    • The memory usage will decrease because 'bool' uses 1 bit/byte per value instead of object pointers
    • The conversion will fail because object types cannot be converted to bool

    Answer: The memory usage will decrease because 'bool' uses 1 bit/byte per value instead of object pointers. The 'object' type stores pointers to Python boolean objects, consuming significant memory. The 'bool' dtype is a primitive, bit-packed representation that is highly efficient. It will not increase memory, and conversion is perfectly valid.

    From lesson: Memory Optimization with dtypes

  146. Which of the following is the most computationally efficient way to double every value in a DataFrame column?

    • for index, row in df.iterrows(): df.at[index, 'col'] *= 2
    • df['col'] = df['col'].apply(lambda x: x * 2)
    • df['col'] = df['col'] * 2
    • df.transform(lambda x: x * 2)

    Answer: df['col'] = df['col'] * 2. Option 3 uses a vectorized operation that executes in optimized C code. Option 1 is a slow loop. Option 2 is a Python-level function call per element. Option 4 is overkill and slower than simple multiplication.

    From lesson: Vectorized Operations vs loops

  147. When performing a conditional calculation like 'if x > 10 then 1 else 0', which approach is preferred in Pandas?

    • Using a for loop with an if-else block
    • Using np.where(df['col'] > 10, 1, 0)
    • Using .apply() with a defined function
    • Converting the series to a list and using list comprehension

    Answer: Using np.where(df['col'] > 10, 1, 0). np.where is a fully vectorized operation. The loop and apply are slower due to Python overhead. Converting to a list removes the benefits of the Pandas index and optimized memory structures.

    From lesson: Vectorized Operations vs loops

  148. Why is df['new'] = df['a'] + df['b'] faster than iterating to sum columns?

    • It uses multiple CPU cores by default for every addition
    • It avoids the overhead of the Python interpreter by using low-level, pre-compiled C loops
    • It stores data in a list instead of a Series
    • It automatically optimizes the memory addresses of the variables

    Answer: It avoids the overhead of the Python interpreter by using low-level, pre-compiled C loops. Vectorized operations push the loop into C code, avoiding the 'Python tax' of checking types and object overhead for every single operation. Option 1 is incorrect as Pandas is primarily single-threaded for basic arithmetic.

    From lesson: Vectorized Operations vs loops

  149. You need to convert a column of strings to uppercase. What is the most 'Pandas-idiomatic' approach?

    • df['col'].str.upper()
    • for i in range(len(df)): df.loc[i, 'col'] = df.loc[i, 'col'].upper()
    • df['col'].apply(str.upper)
    • df['col'].map(lambda x: x.upper())

    Answer: df['col'].str.upper(). The .str accessor is built specifically for vectorized string operations. The other methods involve Python-level iteration which is significantly slower.

    From lesson: Vectorized Operations vs loops

  150. If you are processing a massive dataset that does not fit in RAM, why are loops generally even worse than vectorized operations?

    • Loops require more RAM than vectorized methods
    • Vectorized operations allow for chunking and lazy evaluation that loops cannot easily manage
    • Loops cause memory fragmentation that makes it impossible to save the result
    • Vectorized operations automatically handle parallel data loading from the disk

    Answer: Vectorized operations allow for chunking and lazy evaluation that loops cannot easily manage. Vectorized approaches allow Pandas to interface with libraries like Dask or use chunking techniques. Loops force individual element access, which forces all data into memory at once and prevents batch optimization.

    From lesson: Vectorized Operations vs loops

  151. When processing a 20GB CSV file using chunksize=100000, what is the primary purpose of this approach?

    • To increase the speed of disk I/O operations
    • To ensure the total memory footprint stays below system limits
    • To automatically parallelize the computation across multiple CPU cores
    • To prevent data loss during the reading process

    Answer: To ensure the total memory footprint stays below system limits. Chunking is primarily a memory-management strategy. By reading a subset of rows at a time, you keep memory usage predictable. The other options are incorrect because chunking does not inherently speed up I/O, does not trigger automatic parallelism, and has no relation to preventing data loss.

    From lesson: Chunking Large Files

  152. If you need the total sum of a column 'revenue' from a very large file, which pattern is most memory-efficient?

    • Read all chunks, append them to a list, then call .sum() on the concatenated DataFrame
    • Read all chunks, convert each to a list, then sum
    • Initialize a variable, read each chunk, add the sum of the chunk's column to the variable, and discard the chunk
    • Use read_csv with the 'iterator' parameter and store every chunk in a global dictionary

    Answer: Initialize a variable, read each chunk, add the sum of the chunk's column to the variable, and discard the chunk. Summing per chunk allows you to discard the chunk memory immediately after processing, keeping memory consumption near zero. The other options involve storing the full data in RAM, which defeats the purpose of chunking.

    From lesson: Chunking Large Files

  153. What happens when you call 'for chunk in pd.read_csv('data.csv', chunksize=1000):'?

    • Pandas immediately loads all chunks into memory
    • Pandas returns an iterator that yields one DataFrame of 1000 rows at a time
    • Pandas creates a single DataFrame and hides the extra rows in the index
    • Pandas throws an error because the file isn't fully loaded

    Answer: Pandas returns an iterator that yields one DataFrame of 1000 rows at a time. The 'chunksize' parameter turns the reader into an iterator. The other options are wrong because the data is loaded lazily, not all at once, and it certainly doesn't hide rows or throw errors upon instantiation.

    From lesson: Chunking Large Files

  154. Why might you define 'dtype' when using chunksize?

    • To force Pandas to use the smallest possible data types to save RAM
    • To allow chunking to work on binary files
    • To enable multi-threading on the CSV reader
    • To convert all numeric values into strings automatically

    Answer: To force Pandas to use the smallest possible data types to save RAM. Specifying dtypes helps Pandas avoid guessing (which is memory-intensive) and allows you to downcast (e.g., int64 to int32) to save significant space. The other choices are either irrelevant or technically incorrect outcomes of defining types.

    From lesson: Chunking Large Files

  155. If you are filtering a large dataset (keeping only rows where 'status' == 'active') using chunks, why should you filter inside the loop?

    • It is required by the Pandas API syntax
    • It keeps only the relevant, smaller subset in memory at any given time
    • It makes the original CSV file smaller on the disk
    • It prevents the index from being reset

    Answer: It keeps only the relevant, smaller subset in memory at any given time. Filtering inside the loop ensures that only the filtered data persists in memory for further processing. You cannot shrink the file on disk this way, the API does not force this (though it's best practice), and it is unrelated to index resetting.

    From lesson: Chunking Large Files

  156. What is the primary difference between a Series and a DataFrame in Pandas?

    • A Series is 2D and a DataFrame is 3D
    • A Series is 1D labelled data, a DataFrame is 2D tabular data
    • A Series is for numerical data only, a DataFrame is for mixed types
    • There is no difference; they are interchangeable

    Answer: A Series is 1D labelled data, a DataFrame is 2D tabular data. A Series is a one-dimensional array-like object, while a DataFrame is a two-dimensional structure. Option 0 is wrong because Pandas does not use a 3D standard structure; option 2 is wrong because both handle various data types; option 3 is wrong because they have different methods and structures.

    From lesson: Pandas Interview Questions — Basics

  157. When selecting rows using df.iloc[0:3], which rows are returned?

    • Rows with index labels 0, 1, 2, and 3
    • Rows at integer positions 0, 1, 2, and 3
    • Rows at integer positions 0, 1, and 2
    • Rows with index labels 0, 1, and 2

    Answer: Rows at integer positions 0, 1, and 2. The .iloc indexer is integer-based and uses exclusive slicing at the end, so 0:3 includes 0, 1, and 2. Option 0 and 3 are wrong because .iloc ignores labels, and option 1 is wrong because slicing in Python/Pandas is exclusive of the stop index.

    From lesson: Pandas Interview Questions — Basics

  158. Why is it recommended to use vectorized operations instead of a for-loop on a DataFrame?

    • Vectorized operations are automatically parallelized across all CPU cores
    • Vectorized operations use optimized C-code under the hood to perform calculations on entire arrays at once
    • Vectorized operations allow for modifying the DataFrame in place without copying
    • Vectorized operations are the only way to handle missing data

    Answer: Vectorized operations use optimized C-code under the hood to perform calculations on entire arrays at once. Pandas leverages NumPy's C-based implementations for speed. Option 0 is incorrect because vectorization doesn't inherently imply multi-threading; option 2 is incorrect because vectorization can still create copies; option 3 is incorrect as loops can also handle missing data.

    From lesson: Pandas Interview Questions — Basics

  159. What is the purpose of the 'axis' parameter in operations like mean() or drop()?

    • It defines the coordinate system of the index
    • It determines if the operation is performed along rows (axis 0) or columns (axis 1)
    • It specifies how many rows to process
    • It sets the data type for the calculation

    Answer: It determines if the operation is performed along rows (axis 0) or columns (axis 1). Axis 0 refers to the index (rows), while axis 1 refers to columns. Options 0, 2, and 3 are irrelevant to the functionality of the axis parameter in Pandas aggregation or transformation.

    From lesson: Pandas Interview Questions — Basics

  160. How does the 'inplace=True' parameter affect a Pandas method?

    • It forces the operation to be performed in memory rather than on disk
    • It creates a new object in memory and returns it
    • It modifies the original object directly and returns None
    • It ensures that missing values are ignored during the calculation

    Answer: It modifies the original object directly and returns None. When inplace=True is used, the method changes the original DataFrame and returns None to indicate no new object was created. Option 1 is wrong because Pandas operations are already memory-based; option 3 describes the default behavior (inplace=False); option 4 refers to methods like skipna.

    From lesson: Pandas Interview Questions — Basics

  161. When performing a merge operation, what is the primary difference between a 'left' join and an 'inner' join?

    • Left join keeps all keys from both DataFrames, while inner keeps only keys from the left.
    • Left join keeps all keys from the left DataFrame, while inner keeps only keys present in both.
    • Left join is faster than inner join because it avoids performing an intersection.
    • There is no functional difference; they both return identical results.

    Answer: Left join keeps all keys from the left DataFrame, while inner keeps only keys present in both.. An inner join returns only the intersection of keys, discarding non-matching rows. A left join preserves all rows from the left table, filling missing values with NaN if no match exists in the right table. Other options are incorrect as they misdescribe the scope of the keys preserved.

    From lesson: Pandas Interview Questions — Advanced

  162. What is the computational advantage of using vectorization in Pandas instead of .apply()?

    • Vectorization allows for parallel processing across multiple CPU cores automatically.
    • Vectorization utilizes optimized C code to perform operations on the entire array at once.
    • .apply() is actually faster because it uses a compiled jit compiler internally.
    • Vectorization reduces the memory footprint by converting DataFrames into dictionaries.

    Answer: Vectorization utilizes optimized C code to perform operations on the entire array at once.. Vectorization works by performing operations on whole blocks of memory using compiled C loops, bypassing the Python overhead of .apply(). .apply() essentially runs a Python loop, which is significantly slower.

    From lesson: Pandas Interview Questions — Advanced

  163. Why might a groupby().apply() operation be slower than a groupby().transform() operation?

    • Transform always returns a scalar value for each group while apply returns a Series.
    • Transform is optimized for broadcasting results back to the original index shape, whereas apply may return an aggregated object that requires restructuring.
    • Apply is deprecated in newer versions of Pandas for grouping operations.
    • Transform only works on numeric data while apply works on all data types.

    Answer: Transform is optimized for broadcasting results back to the original index shape, whereas apply may return an aggregated object that requires restructuring.. Transform is highly efficient for calculations that maintain the original shape of the DataFrame (broadcasting), whereas apply often creates new structures that require additional overhead to align back to the original index.

    From lesson: Pandas Interview Questions — Advanced

  164. What happens if you use inplace=True in a method like drop()?

    • It guarantees that the memory address of the object remains the same.
    • It modifies the current DataFrame and returns None rather than a new object.
    • It forces the operation to be performed on disk rather than RAM.
    • It triggers an immediate garbage collection of the old DataFrame.

    Answer: It modifies the current DataFrame and returns None rather than a new object.. inplace=True modifies the object in place, which means the method returns None to prevent chained assignment errors. It does not guarantee memory address stability, nor does it affect disk usage or manual garbage collection.

    From lesson: Pandas Interview Questions — Advanced

  165. Which of the following describes the behavior of a MultiIndex in a DataFrame?

    • It allows for hierarchical indexing, representing data in more than two dimensions.
    • It automatically converts every column into an index for faster lookup.
    • It forces all data types within the DataFrame to become integers.
    • It prevents the use of .loc for data selection.

    Answer: It allows for hierarchical indexing, representing data in more than two dimensions.. MultiIndex allows users to represent high-dimensional data in a 2D tabular format by nesting indices. It does not convert data types, it actually enhances the power of .loc, and it is not a tool to index every column automatically.

    From lesson: Pandas Interview Questions — Advanced

  166. You have a DataFrame 'df'. Which approach is the most efficient way to multiply every value in a numeric column by 2?

    • df['col'] = df['col'].apply(lambda x: x * 2)
    • for i in range(len(df)): df.loc[i, 'col'] *= 2
    • df['col'] = df['col'] * 2
    • df['col'].transform(lambda x: x * 2)

    Answer: df['col'] = df['col'] * 2. Vectorized operations (option 3) are implemented in C and are significantly faster than .apply() (option 1) or loop-based methods (option 2). .transform() is useful for broadcasting grouped results but overkill here.

    From lesson: Pandas Coding Challenges

  167. What happens when you perform 'df1 + df2' where df1 and df2 have different sets of index labels?

    • It raises a KeyError
    • It performs an outer join, resulting in NaNs where labels do not overlap
    • It ignores the index and performs element-wise addition based on position
    • It truncates the result to the intersection of the two indices

    Answer: It performs an outer join, resulting in NaNs where labels do not overlap. Pandas aligns data by index and column labels during binary operations. If a label is missing in one DataFrame, it defaults to NaN (outer join behavior). It does not raise an error, nor does it ignore labels.

    From lesson: Pandas Coding Challenges

  168. Which of the following is the correct way to select rows where 'Age' is greater than 30 and 'City' is 'New York'?

    • df.loc[df['Age'] > 30 and df['City'] == 'New York']
    • df.loc[(df['Age'] > 30) & (df['City'] == 'New York')]
    • df[(df['Age'] > 30) and (df['City'] == 'New York')]
    • df.select(Age > 30, City == 'New York')

    Answer: df.loc[(df['Age'] > 30) & (df['City'] == 'New York')]. Pandas requires bitwise operators (&, |) for boolean indexing because standard Python 'and'/'or' keywords cannot be overloaded for Series. Additionally, parentheses are required to enforce operator precedence.

    From lesson: Pandas Coding Challenges

  169. What is the primary difference between .loc and .iloc?

    • .loc is for labels, while .iloc is for integer-based positional indexing
    • .loc is for rows, while .iloc is for columns
    • .loc is always faster than .iloc
    • .loc supports boolean indexing, but .iloc does not

    Answer: .loc is for labels, while .iloc is for integer-based positional indexing. .loc is label-based, meaning you use the names of the index/columns. .iloc is purely integer-based (0 to n-1). Both support boolean indexing and slicing, so the other options are incorrect.

    From lesson: Pandas Coding Challenges

  170. When grouping data, what does the .agg() method allow you to do that .groupby().sum() does not?

    • It allows calculating the sum of the group only
    • It prevents the grouping index from becoming a column
    • It allows applying different aggregation functions to different columns simultaneously
    • It automatically removes all non-numeric columns from the output

    Answer: It allows applying different aggregation functions to different columns simultaneously. .agg() provides flexibility to pass a dictionary of columns and their corresponding aggregation functions (e.g., {'A': 'sum', 'B': 'mean'}). .groupby().sum() applies the same function to all selected columns.

    From lesson: Pandas Coding Challenges