Quick answer
Most pandas errors come from three concepts, not from pandas being unpredictable. First, view vs copy: chained indexing produces an object that may or may not be connected to the original, which causes SettingWithCopyWarning and silent assignment failures. Second, index type: datetime accessors like .month only exist on a DatetimeIndex, not on a default integer index. Third, alignment: pandas aligns on index labels, so operations across DataFrames produce NaN or duplicated rows when the indexes do not match.
Most pandas errors trace back to a small number of concepts. Once you know which one you are hitting, the fix is usually one line.
1. View vs copy — the silent-failure family
The single most expensive pandas bug: an assignment that succeeds without error and changes nothing.
df[df['age'] > 30]['salary'] = 0 # may silently do nothing
df.loc[df['age'] > 30, 'salary'] = 0 # correctTwo bracket operations in a row create an intermediate object that may be a copy. Assigning to it modifies a temporary that is thrown away.
Rule: if a line both assigns and contains ][, rewrite it with .loc.
Full explanation: fixing SettingWithCopyWarning.
2. Index type — the AttributeError family
Datetime accessors exist only on datetime-typed objects:
df.index.month # AttributeError on a default integer indexConvert first:
df.index = pd.to_datetime(df.index)
df.index.month # works
# For a column, use the .dt accessor:
pd.to_datetime(df['date']).dt.monthNote that Int64Index was removed in pandas 2.0 — use pd.Index with a
dtype check instead. Details:
AttributeError: 'Int64Index' object has no attribute 'month'.
3. Alignment — the NaN and row-explosion family
pandas aligns on index labels, not position. Two consequences:
Unexpected NaN — operating across objects with different indexes:
df_a['x'] + df_b['y'] # NaN wherever labels don't match
df_a['x'].values + df_b['y'].values # positional, no alignmentRow explosion on merge — duplicate keys produce a Cartesian product:
df.merge(other, on='id', validate='one_to_one') # raises instead of multiplyingCheck before merging with df['id'].duplicated().any().
Selecting and removing rows
drop removes by label; boolean masking and filter select. They are not
interchangeable — see
drop rows vs filter, with examples.
Aggregating duplicate rows
Collapsing many rows into one per key is a groupby + agg problem:
collecting repetitive rows into a single row.
Loading many files at once
Read into a list, then concatenate once — never concatenate inside the loop: appending multiple Excel files together.
Plotting several columns
DataFrame.plot() takes a list of columns directly:
plotting multiple pandas columns.
The diagnostic checklist
- Assignment did nothing? → chained indexing. Use
.loc. AttributeErroron.month/.year/.dt? → wrong index or column dtype.pd.to_datetime().- Unexpected
NaN? → index misalignment.reset_index(drop=True)or.values. - Merge returned too many rows? → duplicate join keys. Check
.duplicated(), usevalidate=. - Warning you want to silence? → don't. Confirm the write actually landed first.
Key takeaways
- •If a line both assigns and contains ][, rewrite it with .loc — that single habit removes most SettingWithCopyWarning bugs.
- •Datetime accessors (.month, .year, .dt) require a datetime dtype: convert with pd.to_datetime() before using them.
- •Int64Index was removed in pandas 2.0 — code referencing it directly needs pd.Index or a dtype check instead.
- •pandas aligns on index labels, so unexpected NaN after an operation usually means mismatched indexes, not missing data.
- •A merge that returns more rows than either input means duplicate keys on one side — check with df[key].duplicated().any() before merging.
- •reset_index(drop=True) after filtering or concatenating avoids a whole class of duplicate-label surprises.
Frequently asked questions
Why does my pandas assignment silently do nothing?
You almost certainly used chained indexing, such as df[mask]['col'] = value. The first bracket may return a copy rather than a view, so the assignment lands on a temporary object that is discarded. Use a single .loc call instead: df.loc[mask, 'col'] = value.
Why does .month raise AttributeError on my index?
Datetime accessors only exist on datetime-typed objects. A default DataFrame index is an integer index, which has no .month. Convert first with df.index = pd.to_datetime(df.index), or for a column use pd.to_datetime(df['date']).dt.month.
Why did my merge produce more rows than either DataFrame?
The join key is not unique on at least one side, so pandas produces the Cartesian product of matching rows. Check with df[key].duplicated().any() on both frames, then either deduplicate first or use validate='one_to_one' on merge so pandas raises instead of silently multiplying rows.
Why am I getting NaN after an operation on two DataFrames?
pandas aligns on index labels before operating. If the two objects have different indexes, positions that exist in one but not the other become NaN. Either reset both indexes with reset_index(drop=True) or use .values to operate positionally.
What replaced Int64Index in pandas 2.0?
The specialised Int64Index, Float64Index, and UInt64Index classes were removed in pandas 2.0 in favour of a single pd.Index that carries a dtype. Replace isinstance(idx, pd.Int64Index) with a dtype check such as pd.api.types.is_integer_dtype(idx).