import pandas as pd
nums = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
print(nums)
# -> a b
# -> 0 1 4
# -> 1 2 5
# -> 2 3 6
print(nums.shape) # -> (3, 2) <- 3 rows, 2 columns a b
0 1 4
1 2 5
2 3 6
(3, 2)
A DataFrame is a table: several Series stacked side by side, all sharing one index. Row 2 means the same row in every column, so a ticker, its price and its share count stay together.
In this lesson I build a holdings table from a dictionary of lists, add a position value, and turn those values into portfolio weights.
Each key becomes a column name and each list becomes that column. The lists must be the same length, because they line up row by row. The smallest table that works is two keys and two short lists.
a b
0 1 4
1 2 5
2 3 6
(3, 2)
The same call builds a holdings table, with a text column and two number columns.
df = pd.DataFrame({
"Ticker": ["AAPL", "MSFT", "NVDA", "AMZN"], # each key is a column name
"Close": [185.40, 410.20, 130.75, 178.30], # each list is one column
"Shares": [10, 5, 20, 8],
})
print(df)
# -> Ticker Close Shares
# -> 0 AAPL 185.40 10
# -> 1 MSFT 410.20 5
# -> 2 NVDA 130.75 20
# -> 3 AMZN 178.30 8 Ticker Close Shares
0 AAPL 185.40 10
1 MSFT 410.20 5
2 NVDA 130.75 20
3 AMZN 178.30 8
The numbers down the left are the index. I did not supply one, so pandas numbered the rows 0 to 3.
.shape, .columns, .index and .dtypes are facts about the DataFrame, not data in it. None of them take brackets. .shape reads as (rows, columns).
(4, 3)
4
Index(['Ticker', 'Close', 'Shares'], dtype='str')
RangeIndex(start=0, stop=4, step=1)
Every column carries one type, and .dtypes lists them.
Ticker str
Close float64
Shares int64
dtype: object
Ticker holds text, which pandas 3 reports as str. Close holds decimals, float64. Shares holds whole numbers, int64.
Square brackets with one name hand back that column on its own. That is a Series, the object from Lesson 14, and it carries the same index as the table it came from.
<class 'pandas.Series'>
0 185.40
1 410.20
2 130.75
3 178.30
Name: Close, dtype: float64
Square brackets with a list of names hand back a smaller table.
Ticker Close
0 AAPL 185.40
1 MSFT 410.20
2 NVDA 130.75
3 AMZN 178.30
<class 'pandas.DataFrame'>
One name gives a Series, a list of names gives a DataFrame, even a list of one name.
Close * Shares multiplies the two Series row by row, and assigning the result to a name that does not exist yet adds it as a new column on the right.
Ticker Close Shares Value
0 AAPL 185.40 10 1854.0
1 MSFT 410.20 5 2051.0
2 NVDA 130.75 20 2615.0
3 AMZN 178.30 8 1426.4
(4, 4)
MSFT has the highest price and the fewest shares, and its position is still the second largest.
.sort_values reorders the rows on one column and returns a new table. The index travels with each row, so 2 is still NVDA.
Ticker Close Shares Value
2 NVDA 130.75 20 2615.0
1 MSFT 410.20 5 2051.0
0 AAPL 185.40 10 1854.0
3 AMZN 178.30 8 1426.4
A comparison on a column gives one True or False per row. Feeding that back into square brackets keeps the True rows.
0 False
1 True
2 True
3 False
Name: Value, dtype: bool
Ticker Close Shares Value
1 MSFT 410.20 5 2051.0
2 NVDA 130.75 20 2615.0
Written in one line it is df[df["Value"] > 2000], and it reads as “the rows of df where Value is above 2000”.
Each position over the total gives the share of the portfolio in that holding.
total = df["Value"].sum()
df["Weight"] = df["Value"] / total # each row divided by one number
print(round(total, 2)) # -> 7946.4
print(df)
# -> Ticker Close Shares Value Weight
# -> 0 AAPL 185.40 10 1854.0 0.233313
# -> 1 MSFT 410.20 5 2051.0 0.258104
# -> 2 NVDA 130.75 20 2615.0 0.329080
# -> 3 AMZN 178.30 8 1426.4 0.179503
print(df["Weight"].sum()) # -> 1.07946.4
Ticker Close Shares Value Weight
0 AAPL 185.40 10 1854.0 0.233313
1 MSFT 410.20 5 2051.0 0.258104
2 NVDA 130.75 20 2615.0 0.329080
3 AMZN 178.30 8 1426.4 0.179503
1.0
NVDA is the cheapest share here and the largest weight, at 32.9% of the portfolio.
Using the table from Step 6, print the ticker and weight of every holding under 25% of the portfolio, and print how many rows that leaves.
df["Close"] * df["Shares"] does not pair the two columns by position. It pairs them by index label, then multiplies. Both columns come from the same table, so the labels are identical and the result is what you expect.
Two Series from different tables can carry different labels, and pandas will still align them by label rather than by position. Lesson 24 covers what happens when the labels do not match.