Cars in Qatar
Price distribution of cars by type and engine type.
By Manish Datt
TidyTuesday dataset of 2025-12-09
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import matplotlib.ticker as ticker
import matplotlib.patches as patches
import matplotlib as mpl
qatarcars = pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-12-09/qatarcars.csv')
qatarcars
| origin | make | model | length | width | height | seating | trunk | economy | horsepower | price | mass | performance | type | enginetype | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Germany | BMW | 3 Series Sedan | 4.713 | 1.827 | 1.440 | 5 | 59 | 7.6 | 386 | 164257 | 1653 | 4.3 | Sedan | Petrol |
| 1 | Germany | BMW | X1 | 4.505 | 1.845 | 1.642 | 5 | 505 | 6.6 | 313 | 264000 | 1701 | 5.4 | SUV | Petrol |
| 2 | Germany | Audi | RS Q8 | 5.012 | 1.694 | 1.998 | 5 | 605 | 12.1 | 600 | 630000 | 2490 | 3.6 | SUV | Petrol |
| 3 | Germany | Audi | RS3 | 4.542 | 1.851 | 1.412 | 5 | 321 | 8.7 | 400 | 310000 | 1565 | 3.8 | Sedan | Petrol |
| 4 | Germany | Audi | A3 | 4.456 | 1.960 | 1.416 | 5 | 425 | 6.5 | 180 | 165000 | 1325 | 6.7 | Sedan | Petrol |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 100 | South Korea | Kia | Picanto | 3.595 | 1.595 | 1.495 | 5 | 255 | 6.0 | 84 | 58000 | 976 | 13.7 | Hatchback | Petrol |
| 101 | PR China | Bestune | Oley | 4.555 | 1.660 | 1.465 | 4 | 300 | 6.7 | 101 | 35000 | 1150 | 12.1 | Hatchback | Petrol |
| 102 | Japan | Mitsubishi | Mirage | 3.780 | 1.665 | 1.500 | 5 | 235 | 4.8 | 77 | 44000 | 1050 | 13.9 | Hatchback | Petrol |
| 103 | South Korea | Hyundai | Grand i10 | 3.665 | 1.660 | 1.500 | 5 | 252 | 5.3 | 86 | 46900 | 1840 | 14.5 | Hatchback | Petrol |
| 104 | PR China | MG | MG3 Luxury | 4.055 | 1.729 | 1.504 | 5 | 293 | 6.7 | 109 | 57000 | 1308 | 10.8 | Hatchback | Petrol |
105 rows × 15 columns
def millions_formatter(x, pos):
"""The two args are the value and tick position"""
return f'{x / 1000000:.1f}M'
df_melted = pd.melt(qatarcars, id_vars=['type', 'enginetype'], value_vars=['price'])
df_melted['type_engine'] = df_melted['type'] + '_' + df_melted['enginetype']
df_melted.sort_values(by=['type', 'enginetype'], ascending=False, inplace=True)
df_melted
| type | enginetype | variable | value | type_engine | |
|---|---|---|---|---|---|
| 0 | Sedan | Petrol | price | 164257 | Sedan_Petrol |
| 3 | Sedan | Petrol | price | 310000 | Sedan_Petrol |
| 4 | Sedan | Petrol | price | 165000 | Sedan_Petrol |
| 5 | Sedan | Petrol | price | 1281000 | Sedan_Petrol |
| 19 | Sedan | Petrol | price | 458000 | Sedan_Petrol |
| ... | ... | ... | ... | ... | ... |
| 98 | Coupe | Petrol | price | 224500 | Coupe_Petrol |
| 99 | Coupe | Petrol | price | 125000 | Coupe_Petrol |
| 69 | Coupe | Hybrid | price | 1150000 | Coupe_Hybrid |
| 78 | Coupe | Hybrid | price | 945700 | Coupe_Hybrid |
| 75 | Coupe | Electric | price | 8010200 | Coupe_Electric |
105 rows × 5 columns
df_max = qatarcars.loc[qatarcars.groupby(['type', 'enginetype'])['price'].idxmax()]
df_max.sort_values(by=['type', 'enginetype'], ascending=False, inplace=True)
df_max
| origin | make | model | length | width | height | seating | trunk | economy | horsepower | price | mass | performance | type | enginetype | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5 | Germany | Mercedes | Maybach | 5.469 | 1.921 | 1.510 | 4 | 500 | 13.3 | 612 | 1281000 | 2376 | 4.1 | Sedan | Petrol |
| 68 | UK | Bentley | Flying Spur | 5.316 | 2.013 | 1.483 | 5 | 351 | 10.9 | 521 | 1300000 | 2646 | 3.5 | Sedan | Hybrid |
| 88 | PR China | BYD | Seal | 4.800 | 1.875 | 1.460 | 5 | 400 | NaN | 204 | 164000 | 2185 | 3.8 | Sedan | Electric |
| 6 | Germany | Mercedes | G-Wagon | 4.613 | 1.984 | 1.969 | 5 | 480 | 13.1 | 585 | 1011500 | 2588 | 4.3 | SUV | Petrol |
| 65 | Sweden | Volvo | XC90 | 4.953 | 2.140 | 1.776 | 7 | 71 | 7.1 | 249 | 245000 | 2315 | 5.3 | SUV | Hybrid |
| 7 | Germany | Mercedes | EQS | 5.216 | 1.926 | 1.512 | 5 | 610 | NaN | 333 | 564500 | 2495 | 5.6 | SUV | Electric |
| 28 | Japan | Honda | Civic Type R | 4.594 | 1.890 | 1.407 | 5 | 609 | 9.8 | 315 | 189999 | 1446 | 5.4 | Hatchback | Petrol |
| 18 | Italy | Bugatti | Centodieci | 4.544 | 2.047 | 1.200 | 2 | 0 | 22.5 | 1600 | 33000000 | 1976 | 2.4 | Coupe | Petrol |
| 69 | UK | Bentley | Continental GT | 4.806 | 2.227 | 1.403 | 5 | 235 | 10.9 | 521 | 1150000 | 2459 | 3.3 | Coupe | Hybrid |
| 75 | UK | Lotus | Evija | 4.459 | 2.000 | 1.122 | 2 | 0 | NaN | 1973 | 8010200 | 1887 | 2.9 | Coupe | Electric |
bg_color = '#F5F5F5'
plot_colors = mpl.rcParams["axes.prop_cycle"].by_key()["color"]
fig, ax = plt.subplots(figsize=(8, 4))
sns.swarmplot(data=df_melted, x='type_engine', y='value', log_scale=True, hue='enginetype', legend=False)
ax.yaxis.set_major_formatter(ticker.FuncFormatter(millions_formatter))
plt.ylabel('Qatari Riyal (1M QAR ≈ 275K USD)')
plt.xlabel('')
ax.axvspan(2.5, 5.5, color='#FAFAFA', zorder=0)
ax.axvspan(6.5, 9.25, color='#FAFAFA', zorder=0)
ax.text(1, 25000, "Sedan", ha='center')
ax.text(4, 25000, "SUV", ha='center')
ax.text(6, 25000, "Hatchback", ha='center')
ax.text(8, 25000, "Coupe", ha='center')
# label the max values
for ind, (i, row) in enumerate(df_max.iterrows()):
price_M = f"{row['price']/1000000:.2f}M"
if (row['enginetype'] == 'Petrol'):
ax.text(ind, row['price']*1.2, f"{row['make']}, {row['model']} \n {price_M}", ha='center', color=plot_colors[0])
elif (row['enginetype'] == 'Hybrid'):
if(row['model'] == 'Flying Spur'):
ax.text(ind, row['price']*0.45, f"{row['make']}, {row['model']} \n {price_M}", ha='center', color=plot_colors[1])
else:
ax.text(ind, row['price']*1.2, f"{row['make']}, {row['model']} \n {price_M}", ha='center', color=plot_colors[1])
else:
ax.text(ind, row['price']*1.2, f"{row['make']}, {row['model']} \n {price_M}", ha='center', color=plot_colors[2])
sns.despine(bottom=True, left=True)
ax.tick_params(bottom=False, labelbottom=False)
ax.tick_params(axis='y', which='both', left=False, pad=-5)
ax.grid(True, axis='y', linestyle=':', alpha=0.5)
ax.figure.set_facecolor(bg_color)
ax.set_facecolor(bg_color)
plt.title("Price distribution of qatarcars by type and engine type.\nMax in each category is labelled.", \
ha='left', x=-0.05, fontfamily='Serif', fontsize=14)
ax.annotate(
'Petrol',
xy=(-0.09, 1), xycoords='axes fraction',
xytext=(0.015, 0.10), textcoords='axes fraction', color=plot_colors[0],
rotation=90, fontfamily='monospace'
)
ax.annotate(
'Electric',
xy=(-0.09, 1), xycoords='axes fraction',
xytext=(0.54, 0.03), textcoords='axes fraction', color=plot_colors[2],
rotation=90, fontfamily='monospace'
)
ax.annotate(
'Hybrid',
xy=(-0.09, 1), xycoords='axes fraction',
xytext=(0.83, 0.3), textcoords='axes fraction', color=plot_colors[1],
rotation=90, fontfamily='monospace'
)
plt.tight_layout()
plt.savefig('Qatarcars.png', dpi=300, bbox_inches='tight')
plt.show()