iphone-prices
hi there
Hi! What would you like to analyze?
hard to say
We can start with whatever you have: a dataset, a question, or even a rough idea such as finding trends, making a chart, cleaning data, or building a prediction model.
hmm
No problem. Describe the goal in a sentence, or choose one:
- Explore and summarize a dataset
- Create a chart
- Clean data
- Build a prediction model
- Create a dashboard
/home/piotr/sandbox/mercury-examples/iphone-dashboard load a csv from this path
✓ File activity · Completed · 1 action
Access: Full filesystem
✓ List folder iphone-dashboard completed
/home/piotr/sandbox/mercury-examples/iphone-dashboard
Technical details
list_directory
{"path":"/home/piotr/sandbox/mercury-examples/iphone-dashboard","limit":100}
{"ok": true, "entries": [{"path": "/home/piotr/sandbox/mercury-examples/iphone-dashboard/iphone_prices.csv", "kind": "file"}], "truncated": false, "skipped": 0, "next_offset": null}I’ll load the selected CSV and show a quick preview.
</>Load iphone prices data
import pandas as pd
df = pd.read_csv("/home/piotr/sandbox/mercury-examples/iphone-dashboard/iphone_prices.csv")
print(f"Shape: {df.shape}")
columns = df.columns.tolist()
print(f"Columns ({len(columns)}):", columns if len(columns) <= 100 else columns[:100] + ["..."])
df.head()Shape: (55, 27) Columns (27): ['model', 'release_year', 'screen_size_inches', 'price_usd', 'base_storage_gb', 'advertised_launch_price_usd', 'price_basis', 'notes', 'specification_source_url', 'price_source_url', 'inner_screen_size_inches', 'outer_screen_size_inches', 'form_factor', 'announcement_date', 'scheduled_release_date', 'release_status', 'status_as_of', 'price_source_publisher', 'price_source_type', 'price_source_note', 'price_verification_status', 'price_source_checked_on', 'advertised_launch_price_source_url', 'advertised_price_source_publisher', 'advertised_price_verification_status', 'pricing_conditions_source_url', 'pricing_conditions_source_note']
| model | release_year | screen_size_inches | price_usd | base_storage_gb | advertised_launch_price_usd | price_basis | notes | specification_source_url | price_source_url | ... | price_source_publisher | price_source_type | price_source_note | price_verification_status | price_source_checked_on | advertised_launch_price_source_url | advertised_price_source_publisher | advertised_price_verification_status | pricing_conditions_source_url | pricing_conditions_source_note | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | iPhone (1st generation) | 2007 | 3.5 | 499.0 | 4 | 499 | full_device_contract_required | Original 4GB model. $499 device price; AT&T/Ci... | https://www.apple.com/newsroom/2007/01/09Apple... | https://www.apple.com/newsroom/2007/01/09Apple... | ... | Apple | primary_manufacturer | 4GB device price; service activation and a con... | verified_primary_source | 2026-09-14 | https://www.apple.com/newsroom/2007/01/09Apple... | Apple | verified_primary_source | NaN | NaN |
| 1 | iPhone 3G | 2008 | 3.5 | NaN | 8 | 199 | full_device_price_unverified | $199 was the subsidized two-year-contract pric... | https://www.apple.com/newsroom/2008/06/09Apple... | NaN | ... | NaN | NaN | No verified full-device price. The $199 contra... | missing_full_device_price | 2026-09-14 | https://www.apple.com/newsroom/2008/06/09Apple... | Apple | verified_primary_source | NaN | NaN |
| 2 | iPhone 3GS | 2009 | 3.5 | 599.0 | 16 | 199 | launch_era_full_device | Full-device launch-era retail price; advertise... | https://www.apple.com/newsroom/2009/06/08Apple... | https://arstechnica.com/gadgets/2009/06/att-on... | ... | Ars Technica | contemporaneous_reporting | 16GB no-commitment price; AT&T activation was ... | verified_contemporaneous_reporting | 2026-09-14 | https://www.apple.com/newsroom/2009/06/08Apple... | Apple | verified_primary_source | NaN | NaN |
| 3 | iPhone 4 | 2010 | 3.5 | 599.0 | 16 | 199 | launch_era_full_device | Full-device launch-era retail price; advertise... | https://www.apple.com/newsroom/2010/06/07Apple... | https://osxdaily.com/2010/07/08/iphone-4-price... | ... | OS X Daily | contemporaneous_reporting | 16GB no-commitment price, carrier-locked; not ... | verified_contemporaneous_reporting | 2026-09-14 | https://www.apple.com/newsroom/2010/06/07Apple... | Apple | verified_primary_source | NaN | NaN |
| 4 | iPhone 4S | 2011 | 3.5 | 649.0 | 16 | 199 | launch_era_full_device | Full-device launch-era retail price; advertise... | https://www.apple.com/newsroom/2011/10/04Apple... | https://osxdaily.com/2011/10/07/unlocked-iphon... | ... | OS X Daily | contemporaneous_reporting | 16GB unlocked price; unlocked sales began in N... | verified_contemporaneous_reporting | 2026-09-14 | https://www.apple.com/newsroom/2011/10/04Apple... | Apple | verified_primary_source | NaN | NaN |
5 rows × 27 columns
The CSV contains 55 iPhone records with 27 columns. It includes model names, release years, screen sizes, prices in USD, storage capacity, launch pricing, and supporting source/verification fields. The preview shows early models beginning with the 2007 first-generation iPhone.
please do plot prive vs screen size and lpease add labels for each point
This plots entries with both a recorded price and screen size, labeling every point by iPhone model.
</>Plot price vs screen size
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
plot_df = df.dropna(subset=["screen_size_inches", "price_usd"]).copy()
fig, ax = plt.subplots(figsize=(14, 9))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=70,
color="steelblue",
edgecolor="white",
linewidth=0.8,
alpha=0.9,
)
for _, row in plot_df.iterrows():
ax.annotate(
row["model"],
(row["screen_size_inches"], row["price_usd"]),
xytext=(5, 5),
textcoords="offset points",
fontsize=8,
alpha=0.9,
)
ax.set_title("iPhone Price vs. Screen Size")
ax.set_xlabel("Screen size (inches)")
ax.set_ylabel("Price (USD)")
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25)
plt.tight_layout()
plt.show()The chart uses the available screen_size_inches and price_usd values, with each marker labeled by model. Some records may be absent where either value is missing. The table below provides the exact values represented by the labeled points.
</>Display sorted price table
display(
plot_df[["model", "screen_size_inches", "price_usd"]]
.sort_values(["screen_size_inches", "price_usd", "model"])
.rename(columns={
"model": "Model",
"screen_size_inches": "Screen size (inches)",
"price_usd": "Price (USD)",
})
)| Model | Screen size (inches) | Price (USD) | |
|---|---|---|---|
| 0 | iPhone (1st generation) | 3.5 | 499.0 |
| 2 | iPhone 3GS | 3.5 | 599.0 |
| 3 | iPhone 4 | 3.5 | 599.0 |
| 4 | iPhone 4S | 3.5 | 649.0 |
| 12 | iPhone SE (1st generation) | 4.0 | 399.0 |
| 6 | iPhone 5c | 4.0 | 549.0 |
| 5 | iPhone 5 | 4.0 | 649.0 |
| 7 | iPhone 5s | 4.0 | 649.0 |
| 24 | iPhone SE (2nd generation) | 4.7 | 399.0 |
| 33 | iPhone SE (3rd generation) | 4.7 | 429.0 |
| 8 | iPhone 6 | 4.7 | 649.0 |
| 10 | iPhone 6s | 4.7 | 649.0 |
| 13 | iPhone 7 | 4.7 | 649.0 |
| 15 | iPhone 8 | 4.7 | 699.0 |
| 25 | iPhone 12 mini | 5.4 | 699.0 |
| 29 | iPhone 13 mini | 5.4 | 699.0 |
| 9 | iPhone 6 Plus | 5.5 | 749.0 |
| 11 | iPhone 6s Plus | 5.5 | 749.0 |
| 14 | iPhone 7 Plus | 5.5 | 769.0 |
| 16 | iPhone 8 Plus | 5.5 | 799.0 |
| 22 | iPhone 11 Pro | 5.8 | 999.0 |
| 17 | iPhone X | 5.8 | 999.0 |
| 18 | iPhone XS | 5.8 | 999.0 |
| 46 | iPhone 16e | 6.1 | 599.0 |
| 51 | iPhone 17e | 6.1 | 599.0 |
| 21 | iPhone 11 | 6.1 | 699.0 |
| 20 | iPhone XR | 6.1 | 749.0 |
| 26 | iPhone 12 | 6.1 | 799.0 |
| 30 | iPhone 13 | 6.1 | 799.0 |
| 34 | iPhone 14 | 6.1 | 799.0 |
| 38 | iPhone 15 | 6.1 | 799.0 |
| 42 | iPhone 16 | 6.1 | 799.0 |
| 27 | iPhone 12 Pro | 6.1 | 999.0 |
| 31 | iPhone 13 Pro | 6.1 | 999.0 |
| 36 | iPhone 14 Pro | 6.1 | 999.0 |
| 40 | iPhone 15 Pro | 6.1 | 999.0 |
| 47 | iPhone 17 | 6.3 | 799.0 |
| 44 | iPhone 16 Pro | 6.3 | 999.0 |
| 49 | iPhone 17 Pro | 6.3 | 1099.0 |
| 52 | iPhone 18 Pro | 6.3 | 1199.0 |
| 48 | iPhone Air | 6.5 | 999.0 |
| 23 | iPhone 11 Pro Max | 6.5 | 1099.0 |
| 19 | iPhone XS Max | 6.5 | 1099.0 |
| 35 | iPhone 14 Plus | 6.7 | 899.0 |
| 39 | iPhone 15 Plus | 6.7 | 899.0 |
| 43 | iPhone 16 Plus | 6.7 | 899.0 |
| 28 | iPhone 12 Pro Max | 6.7 | 1099.0 |
| 32 | iPhone 13 Pro Max | 6.7 | 1099.0 |
| 37 | iPhone 14 Pro Max | 6.7 | 1099.0 |
| 41 | iPhone 15 Pro Max | 6.7 | 1199.0 |
| 45 | iPhone 16 Pro Max | 6.9 | 1199.0 |
| 50 | iPhone 17 Pro Max | 6.9 | 1199.0 |
| 53 | iPhone 18 Pro Max | 6.9 | 1299.0 |
| 54 | iPhone Duo | 7.6 | 1999.0 |
The scatter plot maps iPhone price against screen size and labels each point by model. The plotted values span early 3.5-inch models priced from 499 USD to 649 USD, while later 4.0-inch and 4.7-inch models show several overlapping prices—so labels are especially useful for distinguishing models at the same coordinates. Rows missing either price or screen size were excluded.
nice! but please add larger forn, and please make sure that there is no overlap between labels
This redraws the chart with larger label text and automatically repels labels to avoid overlap.
</>Plot price vs screen size
%pip install -q adjustText
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from adjustText import adjust_text
plot_df = df.dropna(subset=["screen_size_inches", "price_usd"]).copy()
fig, ax = plt.subplots(figsize=(16, 10))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=85,
color="steelblue",
edgecolor="white",
linewidth=0.9,
alpha=0.9,
zorder=2,
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"],
fontsize=10,
fontweight="medium",
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.65, lw=0.7),
expand=(1.3, 1.5),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=18, pad=14)
ax.set_xlabel("Screen size (inches)", fontsize=14)
ax.set_ylabel("Price (USD)", fontsize=14)
ax.tick_params(axis="both", labelsize=12)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The updated plot uses larger model labels (font size 10 versus 8 previously) and adjustText to reposition labels automatically. Connector lines link displaced labels to their corresponding points, preserving readability where multiple iPhones share the same screen size and price.
larger fonts please
Increase the label and axis fonts further while retaining the non-overlapping label placement.
</>Plot price vs screen size
fig, ax = plt.subplots(figsize=(18, 12))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=115,
color="steelblue",
edgecolor="white",
linewidth=1.0,
alpha=0.9,
zorder=2,
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"],
fontsize=13,
fontweight="semibold",
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.65, lw=0.9),
expand=(1.5, 1.7),
force_text=(0.8, 1.2),
force_points=(0.5, 0.8),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=24, fontweight="bold", pad=18)
ax.set_xlabel("Screen size (inches)", fontsize=18, labelpad=10)
ax.set_ylabel("Price (USD)", fontsize=18, labelpad=10)
ax.tick_params(axis="both", labelsize=15)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The latest chart uses substantially larger text: model labels are 13 pt, axis labels 18 pt, tick labels 15 pt, and the title 24 pt. The 18 × 12 inch figure also gives adjustText more room to separate the dense labels. The warning only notes that some connector arrows may pass through labels; it does not prevent the larger fonts from rendering.
larger points please, more color, so points are more visuble, and we can make labels as little transparent
Make the points larger and more colorful, while giving labels a light white background so they remain readable without being fully opaque.
</>Plot price vs screen size
import seaborn as sns
fig, ax = plt.subplots(figsize=(18, 12))
palette = sns.color_palette("husl", n_colors=plot_df["screen_size_inches"].nunique())
color_map = dict(zip(sorted(plot_df["screen_size_inches"].unique()), palette))
point_colors = plot_df["screen_size_inches"].map(color_map)
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=220,
c=point_colors,
edgecolor="white",
linewidth=1.4,
alpha=0.95,
zorder=2,
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"],
fontsize=13,
fontweight="semibold",
alpha=0.82,
bbox=dict(facecolor="white", edgecolor="none", alpha=0.55, pad=0.25),
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.5, lw=0.8, shrinkA=8),
expand=(1.5, 1.7),
force_text=(0.8, 1.2),
force_points=(0.5, 0.8),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=24, fontweight="bold", pad=18)
ax.set_xlabel("Screen size (inches)", fontsize=18, labelpad=10)
ax.set_ylabel("Price (USD)", fontsize=18, labelpad=10)
ax.tick_params(axis="both", labelsize=15)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The latest figure uses much more visible points: marker size is 220 with white outlines and high opacity. Colors now vary by screen size, so models at different sizes are easier to distinguish. Labels use 82% text opacity with a 55%-opaque white backing, which keeps dense annotations legible while preserving a lighter visual feel. The figure rendered without the earlier arrow-placement warning.
labels more transparent, points a little larger
Increase marker size slightly and reduce both label and label-background opacity.
</>Plot price vs screen size
fig, ax = plt.subplots(figsize=(18, 12))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=260,
c=point_colors,
edgecolor="white",
linewidth=1.4,
alpha=0.95,
zorder=2,
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"],
fontsize=13,
fontweight="semibold",
alpha=0.68,
bbox=dict(facecolor="white", edgecolor="none", alpha=0.30, pad=0.25),
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.45, lw=0.8, shrinkA=8),
expand=(1.5, 1.7),
force_text=(0.8, 1.2),
force_points=(0.5, 0.8),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=24, fontweight="bold", pad=18)
ax.set_xlabel("Screen size (inches)", fontsize=18, labelpad=10)
ax.set_ylabel("Price (USD)", fontsize=18, labelpad=10)
ax.tick_params(axis="both", labelsize=15)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The latest chart increases point area from 220 to 260, making each colored marker slightly more prominent. Label text opacity is reduced to 0.68 and the white label background to 0.30, so labels are noticeably more transparent while remaining readable against the plot.
what is color meaning in the plot?
Colors represent screen size. Each distinct screen_size_inches value is assigned a different color from the Seaborn "husl" palette:
</>Map colors to screen size
palette = sns.color_palette("husl", n_colors=plot_df["screen_size_inches"].nunique())
color_map = dict(zip(sorted(plot_df["screen_size_inches"].unique()), palette))
point_colors = plot_df["screen_size_inches"].map(color_map)So, models with the same screen size use the same color. However, because screen size is already on the x-axis, the color encoding is redundant. There is currently no legend identifying each color.
Color is assigned from screen_size_inches: each distinct iPhone screen size gets a different hue from Seaborn’s "husl" palette. The same screen-size value therefore uses the same point color.
Because screen size is already encoded by the x-axis, the colors do not add a separate meaning such as model generation or price range; they are redundant and are not explained by a legend.
larger fonts on axes, and pease make markers to the front layer, because sometimes they are behind
This increases axis fonts and draws markers above labels and connector lines.
</>Plot price vs screen size
fig, ax = plt.subplots(figsize=(18, 12))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=260,
c=point_colors,
edgecolor="white",
linewidth=1.4,
alpha=0.95,
zorder=5, # keep markers in the foreground
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"],
fontsize=13,
fontweight="semibold",
alpha=0.68,
bbox=dict(facecolor="white", edgecolor="none", alpha=0.30, pad=0.25),
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.45, lw=0.8, shrinkA=8),
expand=(1.5, 1.7),
force_text=(0.8, 1.2),
force_points=(0.5, 0.8),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=26, fontweight="bold", pad=18)
ax.set_xlabel("Screen size (inches)", fontsize=22, labelpad=12)
ax.set_ylabel("Price (USD)", fontsize=22, labelpad=12)
ax.tick_params(axis="both", labelsize=18)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The latest figure applies the requested presentation changes:
- Axis titles use
fontsize=22and tick labels uselabelsize=18, making both substantially more readable. - Markers use
zorder=5, while model labels usezorder=3, connector lines are below that, and the grid useszorder=1. This keeps the colored markers visibly in front when labels or arrows overlap them.
please remove iPhone from labels to make them shorter
Use shortened model labels while preserving the full names in plot_df:
</>Plot price vs screen size
fig, ax = plt.subplots(figsize=(18, 12))
ax.scatter(
plot_df["screen_size_inches"],
plot_df["price_usd"],
s=260,
c=point_colors,
edgecolor="white",
linewidth=1.4,
alpha=0.95,
zorder=5,
)
texts = [
ax.text(
row["screen_size_inches"],
row["price_usd"],
row["model"].removeprefix("iPhone ").removeprefix("iPhone"),
fontsize=13,
fontweight="semibold",
alpha=0.68,
bbox=dict(facecolor="white", edgecolor="none", alpha=0.30, pad=0.25),
zorder=3,
)
for _, row in plot_df.iterrows()
]
adjust_text(
texts,
ax=ax,
arrowprops=dict(arrowstyle="-", color="gray", alpha=0.45, lw=0.8, shrinkA=8),
expand=(1.5, 1.7),
force_text=(0.8, 1.2),
force_points=(0.5, 0.8),
)
ax.set_title("iPhone Price vs. Screen Size", fontsize=26, fontweight="bold", pad=18)
ax.set_xlabel("Screen size (inches)", fontsize=22, labelpad=12)
ax.set_ylabel("Price (USD)", fontsize=22, labelpad=12)
ax.tick_params(axis="both", labelsize=18)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f} USD"))
ax.grid(True, alpha=0.25, zorder=1)
plt.tight_layout()
plt.show()The chart now uses shortened labels by removing the iPhone prefix—for example, labels appear as 15 Pro, 14, or SE—while the chart title retains the iPhone context.