📌 Snapshot
- Pandas Series and DataFrame made clear: creation, indexing, slicing, arithmetic and CSV files with examples.
- Why Pandas exists when NumPy is there, and how a Series differs from an ndarray.
- Label versus position, and the slicing rule that flips between them: end excluded or included.
- Missing values (NaN) in arithmetic, fill_value, adding and dropping rows and columns, and reading CSV data.
📖 Detailed Notes
Written and reviewed by Rohit Singh, Computer Science & Commerce faculty at UniDrill (MCA, Osmania University · 6 years teaching). Based on NCERT Informatics Practices Class XII, Chapter 2.
A spreadsheet that lives inside Python
NumPy arrays are fast, but a NumPy array has no column names and holds one data type. A class result sheet has student names, subjects, marks and remarks, with text and numbers mixed and labels you want to use. Pandas gives you exactly that: labelled, mixed type, spreadsheet like data that you can load from a CSV file, filter, edit and save, all in a few lines. The name comes from "PANel DATa".
Pandas is built on NumPy, and it works hand in hand with Matplotlib for charts. You install it with pip install pandas (Python must already be installed) and import it with import pandas as pd. The alias pd is only a convention.
Pandas versus NumPy
| Pandas | NumPy |
|---|---|
| A DataFrame can hold different data types (int, float, string, date) | An array holds one data type |
| Simple tools for loading files, selecting, joining and grouping | Focused on numeric array computation |
| Rows and columns have labels, so data is easy to track | Elements are reached by integer position |
| Best for tabular data | Best for numeric array data |
Pandas has two main data structures in this book: the Series (one dimensional) and the DataFrame (two dimensional).
Series: a labelled column
A Series is a one dimensional sequence of values of any type, each with a label called its index. Think of a single spreadsheet column. If you give no index, the labels are the integers 0 to N minus 1.
| Created from | Example | Index you get |
|---|---|---|
| A list | pd.Series([10, 20, 30]) |
0, 1, 2 |
| A list with your own index | pd.Series(["Kavi", "Shyam", "Ravi"], index=[3, 5, 1]) |
3, 5, 1 |
| A string index | pd.Series([2, 3, 4], index=["Feb", "Mar", "Apr"]) |
Feb, Mar, Apr |
| A 1-D NumPy array | pd.Series(np.array([1, 2, 3, 4])) |
0 to 3 |
| A dictionary | pd.Series({'India': 'NewDelhi', 'UK': 'London'}) |
The dictionary keys |
If you pass an index along with an array, its length must equal the number of values. Four values with three labels raise a ValueError (length of values does not match the index).
| Code |
|---|
import pandas as pd |
cap = pd.Series(['NewDelhi', 'WashingtonDC', 'London', 'Paris'], |
index=['India', 'USA', 'UK', 'France']) |
print(cap) |
| Output |
|---|
India NewDelhi |
USA WashingtonDC |
UK London |
France Paris |
dtype: object |
The right hand column shows the values and the left hand column shows the index. Text values give dtype: object.
Reading a Series: position or label
There are two kinds of index. The positional index is the integer position starting at 0. The labelled index is whatever label you assigned. Both work for reading, and a list of them selects several elements.
| Expression | Result |
|---|---|
cap['India'] |
NewDelhi (by label) |
cap[1] |
WashingtonDC (by position) |
cap[['UK', 'USA']] |
UK London and USA WashingtonDC, in that order |
cap[[3, 2]] |
France Paris, then UK London |
cap[1:3] |
USA and UK (position slice, end excluded) |
cap['USA':'France'] |
USA, UK and France (label slice, end included) |
cap[::-1] |
The Series in reverse order |
This is the single most tested rule of the chapter: position slices exclude the end, label slices include it. A way to remember: positions are counted by the machine, which stops before the end; labels are names that you chose on purpose, so the named last one belongs to the answer.
Worked example: slicing and updating. A Series sa has values 10 to 15 and index a to f.
| Code |
|---|
sa = pd.Series([10, 11, 12, 13, 14, 15], index=list('abcdef')) |
sa[1:3] = 50 |
sa['c':'e'] = 500 |
print(sa) |
| Output |
|---|
a 10 |
b 50 |
c 500 |
d 500 |
e 500 |
f 15 |
dtype: int64 |
| Step | Thinking | Effect |
|---|---|---|
| 1 | sa[1:3] is a position slice, so positions 1 and 2, with 3 excluded |
b and c become 50 |
| 2 | sa['c':'e'] is a label slice, so c, d and e |
c, d and e become 500 |
| 3 | The final value of c is 500, since the second statement overwrote the first |
a and f never changed |
The wrong step is treating the label slice like a position slice and stopping at d.
Attributes and methods of a Series
| Item | What it gives | Example |
|---|---|---|
name |
Assigns a name to the Series | cap.name = 'Capitals' |
index.name |
Assigns a name to the index | cap.index.name = 'Countries' |
values |
The data as a NumPy array | cap.values |
size |
Number of values, including NaN | 4 for cap |
empty |
True if the Series has no elements | pd.Series().empty gives True |
head(n) |
First n values; default 5 | s.head(2) |
tail(n) |
Last n values; default 5 | s.tail(2) |
count() |
Number of values that are not NaN | For pd.Series([12, np.nan, 10]) it gives 2 |
Pause and predict: for s = pd.Series([12, np.nan, 10]), what are s.size and s.count()?
Check your answer: size is 3 because the missing value still occupies a slot. count() is 2 because it counts only real values. That gap between the two is the number of missing values.
Arithmetic on Series: matching by label
Operations between two Series pair elements by index label, not by position. This is index matching (alignment). Labels present in only one Series produce NaN (Not a Number), which marks a missing value, so the operation does not fail.
Worked example: addition and subtraction. Let seriesA = pd.Series([1,2,3,4,5], index=['a','b','c','d','e']) and seriesB = pd.Series([10,20,-10,-50,100], index=['z','y','a','c','e']).
| Label | In seriesA | In seriesB | seriesA + seriesB | add with fill_value=0 | sub with fill_value=1000 |
|---|---|---|---|---|---|
| a | 1 | -10 | -9.0 | -9.0 | 11.0 |
| b | 2 | missing | NaN | 2.0 | -998.0 |
| c | 3 | -50 | -47.0 | -47.0 | 53.0 |
| d | 4 | missing | NaN | 4.0 | -996.0 |
| e | 5 | 100 | 105.0 | 105.0 | -95.0 |
| y | missing | 20 | NaN | 20.0 | 980.0 |
| z | missing | 10 | NaN | 10.0 | 990.0 |
| Step | Thinking |
|---|---|
| 1 | The result has the union of both indexes: a, b, c, d, e, y, z (7 labels, sorted) |
| 2 | Labels in both (a, c, e) are computed normally |
| 3 | Labels in one only (b, d, y, z) give NaN with the operator |
| 4 | The methods add, sub, mul and div accept fill_value, which replaces a missing side before calculating. With seriesA.add(seriesB, fill_value=0), seriesB has no value at label b, so it is treated as 0 and we get 2 + 0 = 2 |
| 5 | The result is float, since NaN is a float value |
Division shows why the fill value matters: seriesA.div(seriesB, fill_value=0) gives inf at labels b and d (2 divided by 0 and 4 divided by 0) and 0.0 at y and z (0 divided by 20 and by 10). The operators + - * / are equivalent to add sub mul div without a fill value. Note that fill_value is applied only where one side is missing, not where both are.
Beyond the textbook. For the exam: a Series can be read as cap[1] by position or cap['USA'] by label. In reality: newer versions of Pandas warn when an integer is used on a Series whose index is not made of integers, because it is ambiguous whether you mean the label or the position, and they are moving towards treating such integers as labels only. The clear forms are cap.iloc[1] for position and cap.loc['USA'] for label. Why it matters: when your index itself is made of integers, as in pd.Series([...], index=[3, 5, 1]), s[1] means the label 1, not the second element.
Series versus NumPy ndarray
| Series | NumPy ndarray |
|---|---|
| Has labelled indexes that you can define (numbers or letters) | Accessed only by integer position |
| Operations align on labels; unmatched labels give NaN | Operations need matching shapes; there is no NaN based alignment |
| Can be reversed or indexed by labels in any order | Index starts at 0 and is fixed |
| Needs more memory | Needs less memory |
DataFrame: a table with row and column labels
A DataFrame is a two dimensional labelled structure like a table in MySQL or a spreadsheet. It has a row index and a column index, and each column can hold a different data type. Each column of a DataFrame is a Series, and a DataFrame can be thought of as a dictionary of Series.
| Created from | How it behaves |
|---|---|
| Nothing | pd.DataFrame() gives an empty DataFrame |
| A NumPy array or arrays | Each array becomes a row; shorter ones are padded with NaN |
| A list of dictionaries | One row per dictionary; keys become columns; missing keys give NaN |
| A dictionary of lists | Keys are column labels and each list is a column |
| A list of Series | Each Series is a row; its labels become columns (union of labels) |
| A dictionary of Series | Keys are column labels; the row labels are the union of all the Series indexes |
Worked example: dictionary of lists.
| Code |
|---|
dictForest = {'State': ['Assam', 'Delhi', 'Kerala'], |
'GArea': [78438, 1483, 38852], |
'VDF': [2797, 6.72, 1663]} |
dFrameForest = pd.DataFrame(dictForest) |
print(dFrameForest) |
| Output |
|---|
State GArea VDF |
0 Assam 78438 2797.00 |
1 Delhi 1483 6.72 |
2 Kerala 38852 1663.00 |
| Thinking | Result |
|---|---|
| The dictionary has 3 keys, so 3 columns | Columns State, GArea, VDF |
| Each list has 3 values, so 3 rows | Row labels 0, 1, 2 by default |
| VDF mixes ints and a float, so the column is promoted to float | 2797.00, 6.72, 1663.00 |
| Different columns may have different types | State holds text, GArea integers |
Passing columns=['State', 'VDF', 'GArea'] reorders the columns. For a list of dictionaries, [{'a': 10, 'b': 20}, {'a': 5, 'b': 10, 'c': 20}] gives 2 rows and 3 columns (a, b, c), with NaN in column c of the first row. The number of columns is the maximum number of keys in any dictionary.
Now take a dictionary of Series holding marks, with the subjects as the row index:
| Code |
|---|
ResultSheet = { |
'Arnab': pd.Series([90, 91, 97], index=['Maths', 'Science', 'Hindi']), |
'Ramit': pd.Series([92, 81, 96], index=['Maths', 'Science', 'Hindi']), |
'Samridhi': pd.Series([89, 91, 88], index=['Maths', 'Science', 'Hindi']), |
'Riya': pd.Series([81, 71, 67], index=['Maths', 'Science', 'Hindi']), |
'Mallika': pd.Series([94, 95, 99], index=['Maths', 'Science', 'Hindi'])} |
ResultDF = pd.DataFrame(ResultSheet) |
| Arnab | Ramit | Samridhi | Riya | Mallika | |
|---|---|---|---|---|---|
| Maths | 90 | 92 | 89 | 81 | 94 |
| Science | 91 | 81 | 91 | 71 | 95 |
| Hindi | 97 | 96 | 88 | 67 | 99 |
The students are the columns and the subjects are the rows, because the dictionary keys became the column labels.
Attributes of a DataFrame
| Attribute | What it gives |
|---|---|
index |
Row labels |
columns |
Column labels |
dtypes |
Data type of each column |
values |
A NumPy array of the data without labels |
shape |
Tuple (rows, columns) |
size |
Total number of values (rows times columns) |
T |
The transpose: rows and columns exchange places |
head(n), tail(n) |
First or last n rows (default 5) |
empty |
True if there is no data |
For ResultDF the shape is (3, 5) and the size is 15. For the forest data in the next table, shape is (4, 3) and size is 12.
Rows and columns: add, change, delete, rename
| Task | Code | Notes |
|---|---|---|
| Add a column | ResultDF['Preeti'] = [89, 78, 76] |
A new label creates a new column at the end. An existing label updates the column |
| Set a whole column to one value | ResultDF['Arnab'] = 90 |
Every row of that column becomes 90 |
| Add a row | ResultDF.loc['English'] = [85, 86, 83, 80, 90, 89] |
A new label creates a row. An existing label updates that row |
| Delete a row | ResultDF.drop('Science', axis=0) |
axis=0 means rows |
| Delete columns | ResultDF.drop(['Ramit', 'Riya'], axis=1) |
axis=1 means columns |
| Rename rows | ResultDF.rename({'Maths': 'Sub1'}, axis='index') |
Labels you do not mention stay unchanged |
| Rename columns | ResultDF.rename({'Arnab': 'Student1'}, axis='columns') |
Same rule |
Wrong lengths are errors: a new row with fewer values than columns raises ValueError (mismatched columns), and a new column with fewer values than rows raises ValueError (length does not match the index). Also, drop and rename return a new DataFrame, so you assign the result back (ResultDF = ResultDF.drop('Science', axis=0)), otherwise nothing seems to change. If several rows share the same label, drop removes all of them.
Worked example: update or add? ResultDF has rows Maths, Science and Hindi. What happens with ResultDF.loc['English'] = [85, 86, 83, 80, 90, 89] (after the Preeti column was added), and then the same line with different marks?
| Step | Thinking | Result |
|---|---|---|
| 1 | English is not an existing row label |
A new 4th row is added |
| 2 | Run again with new numbers | English now exists, so that row is updated, not duplicated |
| 3 | ResultDF.loc['Maths'] = 0 |
Every value in the Maths row becomes 0 |
Accessing data with loc
DataFrame.loc[] selects by labels. A single row label returns that row as a Series. A single column label (after a colon) returns the column as a Series. A list of labels returns a DataFrame.
| Expression | Returns |
|---|---|
ResultDF.loc['Science'] |
Row Science as a Series |
ResultDF.loc[:, 'Arnab'] |
Column Arnab as a Series (same as ResultDF['Arnab']) |
ResultDF.loc[['Science', 'Hindi']] |
A DataFrame of those two rows |
ResultDF.loc['Maths':'Science'] |
Rows Maths and Science (label slice, end included) |
ResultDF.loc['Maths':'Science', 'Arnab':'Samridhi'] |
2 rows by 3 columns |
ResultDF.loc['Maths':'Science', ['Arnab', 'Samridhi']] |
2 rows by 2 chosen columns |
ResultDF.loc[[True, False, True]] |
Rows Maths and Hindi (Boolean list: True keeps the row) |
ResultDF.loc['Maths'] > 90 |
True or False for each student's Maths mark |
Boolean indexing works on the actual values. ResultDF.loc['Maths'] > 90 returns False for Arnab (90), True for Ramit (92), False for Samridhi (89), False for Riya (81) and True for Mallika (94). Notice the rule again: slicing in a DataFrame with loc includes the end label. If you ask for a label that does not exist, you get a KeyError.
Pause and predict: for the ResultDF above, what does ResultDF.loc['Maths':'Hindi', 'Riya'] return?
Check your answer: The Riya column for all three rows from Maths to Hindi (the end label is included), as a Series: 81, 71 and 67. Because the column is a single label, you get a Series and not a DataFrame.
Pause and predict: for pd.DataFrame([10, 20, 30, 40, 50]), what does .loc[2] return, the third row or the row labelled 2?
Check your answer: The row labelled 2, which happens to be the third row here: the value 30. In loc, an integer is always a label, never a position.
Joining DataFrames with append
The chapter uses append() to attach the rows of one DataFrame below another. Columns that are missing in the first one are added, and gaps become NaN.
| Code |
|---|
dFrame1 = dFrame1.append(dFrame2) |
With dFrame1 having columns C1, C2, C3 and dFrame2 having C2, C5, the result has four columns C1, C2, C3, C5 and all the rows of both, with NaN wherever a table had no value. Two parameters appear in questions:
| Parameter | Effect |
|---|---|
ignore_index=True |
Row labels are discarded and renumbered 0, 1, 2 and so on |
verify_integrity=True |
Raises an error if the row labels repeat (default False, so duplicate labels such as R2 are allowed) |
sort=True |
Sorts the column labels |
Beyond the textbook. For the exam: DataFrame.append() merges two DataFrames. In reality: append() was removed in Pandas 2.0, and current code uses pd.concat([dFrame1, dFrame2]), with the same ignore_index option. The result is identical. Why it matters: if code copied from a book fails with "DataFrame has no attribute append", this is the reason.
Importing and exporting CSV files
A CSV (comma separated values) file is a plain text file where each line is a record and values are separated by commas. Spreadsheet programs open it directly.
| Task | Code | Key parameters |
|---|---|---|
| CSV to DataFrame | marks = pd.read_csv("ResultData.csv", sep=",", header=0) |
sep is the separator (default is a comma). header=0 uses the first line as column names. names=[...] gives your own column names |
| DataFrame to CSV | ResultDF.to_csv("resultout.csv", sep=",") |
header=False leaves out the column names. index=False leaves out the row labels |
Worked example: import then export. The file ResultData.csv has the header RollNo,Name,Eco,Maths and three lines of data.
| Code |
|---|
marks = pd.read_csv("ResultData.csv", header=0) |
print(marks.shape) |
marks.to_csv("out.txt", sep='@', header=False, index=False) |
| Step | Thinking | Result |
|---|---|---|
| 1 | header=0 makes the first line the column names |
Columns RollNo, Name, Eco, Maths |
| 2 | Three data lines become three rows | shape is (3, 4) |
| 3 | In the export, sep='@' replaces the commas |
Lines look like 1@Arnab@18@57 |
| 4 | header=False and index=False |
No column names and no row numbers in the file |
The wrong step is forgetting index=False: the output then begins every line with the row label 0, 1, 2.
Beyond the textbook. For the exam: read_csv takes the file path, sep and header. In reality: when the first line is not a header, pass header=None and supply names, otherwise the first data line is eaten as column titles. Why it matters: a CSV with an unexpected header shows up as wrong column names and one fewer row than you expected.
Exam traps
| Trap | Why it is tempting | The truth |
|---|---|---|
"s[1:3] and s['b':'d'] behave the same" |
Both are slices | Position slice excludes the end; label slice includes it |
| "Adding two Series adds by position" | NumPy does | Pandas matches by label; unmatched labels give NaN |
| "NaN is a string" | It prints as text | NaN is a missing floating point value, so a column with NaN becomes float |
"size and count() are equal" |
Both count values | size includes NaN; count() excludes it |
"drop('Maths') changes the DataFrame" |
It sounds in place | It returns a new DataFrame; assign it back |
"axis=0 drops columns" |
Axis 0 sounds like the first thing | axis=0 is rows; axis=1 is columns |
| "A dictionary key becomes a row label in a DataFrame" | It is in the first position | Keys become column labels |
"ResultDF['New'] = [1, 2] on 3 rows works" |
Python allows short lists elsewhere | Values must match the number of rows, otherwise ValueError |
60-second revision
| Topic | Remember |
|---|---|
| Libraries | NumPy for arrays, Pandas for tables, Matplotlib for charts |
| Series | 1-D, labelled index; default index 0 to N minus 1 |
| Create Series | List, ndarray, dictionary (keys become the index) |
| Slicing | Position: end excluded. Label: end included |
| Series attributes | name, index.name, values, size, empty |
| Series methods | head, tail, count (non NaN) |
| Arithmetic | Aligns on labels, NaN for unmatched; fill_value in add sub mul div |
| DataFrame | 2-D; dictionary keys become columns |
loc |
Label based; slices include the end |
| Add and delete | df['col'] = ..., df.loc['row'] = ..., drop(label, axis=0/1) |
axis |
0 is rows, 1 is columns |
| CSV | read_csv(sep, header, names); to_csv(sep, header, index) |
Where this chapter connects
A Series is a labelled version of the array from Introduction to NumPy, and the idea of slicing, shape and element wise operations carries over directly. The next chapter, Data Handling using Pandas - II, adds descriptive statistics, sorting, grouping and more operations on DataFrames, and Plotting Data using Matplotlib turns these tables into charts. A DataFrame is also the Python version of the SQL tables you queried in Querying and SQL Functions.
🎯 Practice MCQs
First 3 questions free · create a free account to unlock the rest — answers & explanations included, no payment needed
Q1. Consider: (1) import pandas as pd (2) s = pd.Series([10, 20, 30]) (3) print(s[2]). What is printed?
▸ Show answer & explanation
Answer: B
With no index given, the labels are 0, 1 and 2, so the label 2 refers to the third value, 30. Option A is the value at label 1, and option C is the label, not the value.
Q2. A Series is created with pd.Series({'India': 'NewDelhi', 'UK': 'London', 'Japan': 'Tokyo'}). What forms the index of the Series?
▸ Show answer & explanation
Answer: C
When a Series is created from a dictionary, the dictionary keys become the index and the values become the data. The default 0, 1, 2 index (A) is used only when no labels are available.
Q3. Consider: (1) import numpy as np (2) arr = np.array([1, 2, 3, 4]) (3) s = pd.Series(arr, index=['Jan', 'Feb', 'Mar']). What happens?
▸ Show answer & explanation
Answer: C
The number of index labels must equal the number of values. Four values with three labels cause a ValueError. Pandas does not invent a fourth label (B) or drop data (A and D).
🔒 12 more practice MCQs
Create a free account to unlock every MCQ in this chapter — answers and explanations included. No payment needed.
Already registered? Just log in and they'll all appear here.
Q4. A Series s has values 2, 3, 4, 5 with index labels a, b, c, d. How many elements are in s['b':'d'] and in s[1:3] respectively?
▸ Show answer & explanation
Answer: A
A label slice includes its end label, so b, c and d give 3 elements. A position slice excludes its end position, so positions 1 and 2 give 2 elements. The other options apply one rule to both slices.
Q5. Series a has index x, y with values 1, 2. Series b has index y, z with values 10, 20. What is the result of a + b?
▸ Show answer & explanation
Answer: B
Addition matches by label. Only y is in both, giving 2 + 10 = 12.0. Labels x and z exist in one Series only, so they give NaN. Option C is the result of add with fill_value=0.
Q6. For the Series in the previous question, what does a.add(b, fill_value=0) give?
▸ Show answer & explanation
Answer: B
fill_value replaces a missing value on one side with 0 before the addition. So x is 1 + 0, y is 2 + 10 and z is 0 + 20. Option A is the result without fill_value.
Q7. Match List I with List II. List I: (i) df.shape (ii) df.size (iii) df.T (iv) df.empty. List II: (p) rows and columns exchanged (q) True if there is no data (r) a tuple of rows and columns (s) total number of values.
▸ Show answer & explanation
Answer: A
shape is a tuple (rows, columns), size is the total count of values, T is the transpose and empty reports whether the DataFrame has no data. The other options mismatch at least one pair.
Q8. A DataFrame has 4 rows and 3 columns. What are its shape and size?
▸ Show answer & explanation
Answer: C
shape lists rows first, so (4, 3), and size is the product 4 times 3, which is 12. Option A reverses the shape, and option B adds instead of multiplying.
Q9. Consider: (1) d = {'A': [1, 2, 3], 'B': [4, 5, 6]} (2) df = pd.DataFrame(d). What are the column labels of df?
▸ Show answer & explanation
Answer: C
When a DataFrame is built from a dictionary of lists, the keys become the column labels and each list becomes a column. The row labels are 0, 1 and 2 by default.
Q10. A DataFrame df has row labels Maths, Science, Hindi and English. How many rows does df.loc['Maths':'Hindi'] return?
▸ Show answer & explanation
Answer: C
Label based slicing with loc includes the end label, so Maths, Science and Hindi are returned. A position slice would have excluded the end, which is not how loc behaves.
Q11. Which statement deletes the column named Ramit from the DataFrame ResultDF and stores the result back?
▸ Show answer & explanation
Answer: B
Columns are removed with axis=1. Option A uses axis=0, which looks for a row labelled Ramit. Option C does not store the returned DataFrame, and delete is not a DataFrame method for this.
Q12. A DataFrame already has a row labelled English. What does the statement ResultDF.loc['English'] = [95, 86, 95, 80, 95, 99] do, assuming the DataFrame has six columns?
▸ Show answer & explanation
Answer: C
Assigning with loc to an existing row label updates that row and does not create a duplicate. A new row is created only for a label that is not present.
Q13. Assertion (A): When two Series with different index labels are added, Pandas does not raise an error. Reason (R): Pandas aligns the Series on their index labels and marks missing positions as NaN.
▸ Show answer & explanation
Answer: A
Because the operation is aligned by labels and unmatched labels become NaN, the addition completes without an error. The reason describes exactly the mechanism behind the assertion.
Q14. A Series is created by pd.Series([12, np.nan, 10]). What are its size and count()?
▸ Show answer & explanation
Answer: D
size counts every slot including the NaN, so it is 3. count() counts only the non missing values, so it is 2. Option C reverses the two.
Q15. Which statement saves the DataFrame df to a CSV file separated by commas without writing the row labels?
▸ Show answer & explanation
Answer: A
to_csv writes a DataFrame to a file, and index=False leaves out the row labels. Option B uses read_csv, which is for reading and is a function of pd, not of the DataFrame. Option C omits the column names rather than the row labels. Option D calls to_csv on the module, where it does not exist.
📊 Previous-Year Questions
Practise with real CUET Computer Science previous-year papers — every question solved, with the correct answer and a step-by-step explanation.
View solved CUET PYQ papers →Ready to drill Computer Science?
Unlock all MCQs, chapter tests, mocks & PYQs for ₹199/year.
Get UniDrill Pro