Introduction

This dataset is a collection of Airbnb listings throughout Madrid, Spain, including the surrounding region. It is a detailed dataset that provides information about each listing, including the host, location, coordinates, price, number of reviews, and other details. This dataset caught my interest because I studied abroad in Madrid during the Spring 2026 semester. I loved living in the city for almost four months, so having the opportunity to analyze data from a place I enjoyed so much was exciting. Being able to apply my data analytics skills to a city I have a personal connection with made this project even more interesting to me. At first, I wanted to see where the highest concentration of Airbnb listings was located and which districts had the highest prices. I was also interested in finding out which areas were more affordable, so that on future trips to Madrid, I would know where to look for an Airbnb without spending too much money!

For this project, I created several visualizations to better understand where Airbnb listings were concentrated and how prices varied across different districts in Madrid. I used a scatter plot to show where the more expensive listings were located based on their latitude and longitude coordinates. I also used Folium to create an interactive map of the first 100 listings to get a better idea of where they were located throughout the city. Another visualization I created was a bar graph comparing the average Airbnb price in each district to the overall average price across the Madrid region. I also used a line plot to explore whether listings with more reviews tended to have higher prices. In addition, I created a multi-line plot to compare the different room types available in each district. Lastly, I used a pie chart to show the overall distribution of room types across all Airbnb listings in the dataset. These visualizations helped me explore the different pricing trends, locations, and types of Airbnb listings throughout Madrid.

import pandas as pd
import matplotlib.pyplot as plt 
import numpy
import warnings
warnings.filterwarnings("ignore")
df = pd.read_csv(r"C:\Users\markp\Downloads\listings.csv")
df.head()
type(df)
df.columns

The Data

This dataset had several problems that made creating visualizations more difficult. One of the biggest issues was that many of the listings had missing price values (NaN). To fix this, I removed the listings with missing pricing data so I could perform a more accurate analysis of the average Airbnb prices across different districts. Keeping these missing values in the dataset would have made it more difficult to calculate the mean prices and create accurate visualizations. Cleaning the data helped me get more reliable results and made it easier to compare Airbnb prices throughout Madrid.

df.columns
## Index(['id', 'name', 'host_id', 'host_name', 'neighbourhood_group',
##        'neighbourhood', 'latitude', 'longitude', 'room_type', 'price',
##        'minimum_nights', 'number_of_reviews', 'last_review',
##        'reviews_per_month', 'calculated_host_listings_count',
##        'availability_365', 'number_of_reviews_ltm', 'license'],
##       dtype='str')
df.info()
## <class 'pandas.DataFrame'>
## RangeIndex: 26980 entries, 0 to 26979
## Data columns (total 18 columns):
##  #   Column                          Non-Null Count  Dtype  
## ---  ------                          --------------  -----  
##  0   id                              26980 non-null  int64  
##  1   name                            26980 non-null  str    
##  2   host_id                         26980 non-null  int64  
##  3   host_name                       26977 non-null  str    
##  4   neighbourhood_group             26980 non-null  str    
##  5   neighbourhood                   26980 non-null  str    
##  6   latitude                        26980 non-null  float64
##  7   longitude                       26980 non-null  float64
##  8   room_type                       26980 non-null  str    
##  9   price                           21068 non-null  float64
##  10  minimum_nights                  26980 non-null  int64  
##  11  number_of_reviews               26980 non-null  int64  
##  12  last_review                     21283 non-null  str    
##  13  reviews_per_month               21283 non-null  float64
##  14  calculated_host_listings_count  26980 non-null  int64  
##  15  availability_365                26980 non-null  int64  
##  16  number_of_reviews_ltm           26980 non-null  int64  
##  17  license                         2630 non-null   str    
## dtypes: float64(4), int64(7), str(7)
## memory usage: 5.9 MB
df = df.dropna(subset=['price'])
df = df[df['price'] > 0]
df.isna().sum()
df.info()

Findings

Plot 1: Where are the top 1000 lisitngs from the dataset located on a map

import folium

my_map = folium.Map(location=[40.4168, -3.7038], zoom_start=12)

for lat, lon in df[['latitude', 'longitude']].dropna().head(1000).values:
    folium.CircleMarker(
        location=[lat, lon],
        radius=2,
        color='blue'
    ).add_to(my_map)
my_map
Make this Notebook Trusted to load map: File -> Trust Notebook

The map shows that most of the first 100 Airbnb listings in the dataset are located in the center of Madrid, which is known as the Centro district. From looking at the map, there appears to be a higher concentration of Airbnb listings in the central part of the city compared to the surrounding areas. This is likely because Centro is one of the main tourist areas in Madrid, with many popular attractions, restaurants, and shops nearby. Tourists may prefer staying in this area because it provides easier access to many of the city’s main attractions. Many of Madrid’s most popular tourist destinations, such as Plaza Mayor, Puerta del Sol, and the Royal Palace, are located in or near this district. This could explain why there is a larger concentration of Airbnb listings around these areas, as visitors often look for accommodations within walking distance of major attractions. Overall, the map suggests that location plays an important role in where Airbnb listings are concentrated throughout Madrid, especially in areas that attract a large number of tourists.

Plot 2 : Heatmap Scatter Plot for Locations

plt.figure(figsize=(12, 10))

plt.scatter(
    df['longitude'],
    df['latitude'],
    c=df['price'],
    cmap='coolwarm',
    alpha=0.5,
    vmin=0,
    vmax=500
)

plt.colorbar(label='Airbnb Price (€)')
plt.title('Airbnb Prices by Location in Madrid')
plt.xlabel('Longitude')
plt.ylabel('Latitude')

plt.show()

This graph also shows that there is a larger concentration of Airbnb listings in the center of Madrid, where many of the more expensive listings are located. The use of warm and cool colors in the scatter plot helps show the differences in pricing throughout the city. I created this visualization to focus on Airbnb listings priced over 500 euros and to see where the higher-priced listings are concentrated. From looking at the graph, it appears that many of the more expensive listings are located in areas that attract a large number of tourists. For example, Puerta del Sol is one of the most popular tourist destinations in Madrid, which could explain why there are more expensive Airbnb listings around this area. Another interesting finding is that the outskirts of Madrid have a much smaller concentration of Airbnb listings compared to the inner city. Overall, this graph suggests that Airbnb prices and the number of listings are influenced by location, with central tourist areas having more listings and a greater concentration of higher-priced accommodations.

Plot 3: Average Price of listing by District

def pick_colors_to_mean(this_data):
    colors = []
    avg = this_data.mean()

    for each in this_data:
        if each > avg:
            colors.append('red')  # Above average price
        elif each < avg:
            colors.append('green')  # Below average price
        else:
            colors.append('blue')  # Equal to average

    return colors
avg_price = df.groupby('neighbourhood_group')['price'].mean().sort_values()

colors = pick_colors_to_mean(avg_price)

plt.figure(figsize=(12, 12))

plt.barh(avg_price.index, avg_price.values, color=colors)

plt.axvline(avg_price.mean(), color='black',label='District Average')

plt.legend(loc='lower right', fontsize=14)
plt.title('Average Airbnb Price by District in Madrid')
plt.xlabel('Average Price (€)')
plt.ylabel('District')

plt.show()

Based on this graph, the most expensive district in Madrid for Airbnb listings is Salamanca, while the cheapest district is Vicálvaro. We can see that Salamanca has an average Airbnb price that is almost 60 euros higher than the average of all the districts in Madrid. The graph also uses different colors to help show which districts are above or below the average. Any district shown in green has an average Airbnb price below the district average for Madrid. These districts would be better options for tourists looking for more affordable Airbnb accommodations because their listings are cheaper on average. On the other hand, districts above the average tend to have more expensive Airbnb listings. Overall, this graph helps show the differences in Airbnb pricing throughout Madrid and how choosing a different district could help tourists save money on accommodations.

Plot 4: Average Price based on reviews

avg_reviews = df.groupby('number_of_reviews')['price'].mean()

plt.figure(figsize=(12, 8))

plt.plot(avg_reviews.index,avg_reviews.values,color='blue',marker='o',markersize=3)

plt.title('Average Airbnb Price by Number of Reviews in Madrid')
plt.xlabel('Number of Reviews')
plt.ylabel('Average Price (€)')
plt.grid(True)

plt.show()

This graph shows the relationship between the number of reviews an Airbnb listing receives and its average price in Madrid. From looking at the graph, we can see that most of the average prices are concentrated between 50 euros and 200 euros, regardless of the number of reviews. There are also some significant outliers where the average price is much higher than the rest of the listings. For example, around 600 reviews, there is an average price reaching approximately 700 euros, while another point around 325 reviews reaches over 600 euros. Another interesting finding is that listings with a higher number of reviews do not necessarily have higher average prices. I thought this was especially interesting because I would have expected more expensive listings to receive more reviews from people discussing whether the higher price was worth it or not. I also believe that the pricing data could be right-skewed because many of the Airbnb listings are located in the center of Madrid, where prices tend to be higher due to tourism and the popularity of the area. These more expensive listings could be pulling the average prices higher and creating some of the outliers we see in the graph. Overall, this graph suggests that the number of reviews does not necessarily determine the price of an Airbnb listing, and other factors, especially location, could have a greater influence on pricing.

Plot 5: Airbnb Listings by Room Type and District

room_counts = df.groupby(['neighbourhood_group', 'room_type']).size().unstack()

plt.figure(figsize=(12, 8))


for room in room_counts.columns:
    plt.plot(room_counts.index,room_counts[room], marker='o',label=room)

plt.title('Airbnb Listings by District and Room Type')
plt.xlabel('District')
plt.ylabel('Number of Listings')
plt.xticks(rotation=90)
plt.legend()
plt.grid(True)

plt.show()

This graph shows the number of Airbnb listings in each district of Madrid based on the different room types available. From looking at the graph, we can see that entire homes and apartments are the most common type of Airbnb listing throughout Madrid, followed by private rooms. The Centro district has by far the largest number of listings, with over 7,000 entire homes and apartments and around 1,700 private rooms. This is significantly higher than any other district in Madrid.

Another interesting finding is that districts such as Salamanca, Chamberí, and Tetuán also have a relatively high number of entire home and apartment listings compared to other districts. On the other hand, districts such as Vicálvaro, Barajas, and Moratalaz have much fewer Airbnb listings. The graph also shows that hotel rooms and shared rooms make up a very small portion of the listings compared to entire homes and private rooms. Overall, this graph suggests that the location of a district plays an important role in the number and types of Airbnb accommodations available. The central districts tend to have more listings, while districts farther away from the city center generally have fewer options for tourists.

Bonus Plots: Boxplot and Pie Chart

room_counts = df['room_type'].value_counts()
plt.figure(figsize=(10, 8))

plt.pie(room_counts,
        autopct=lambda p: f'{p:.1f}%' if p > 3 else '')
plt.title('Airbnb Listings by Room Type in Madrid')

plt.legend(room_counts.index, title='Room Type',
           loc='upper right', bbox_to_anchor=(1.3, 1))

plt.tight_layout()
plt.show()

df_filtered = df[df['price'] < 500]


df_filtered.boxplot(
    column='price',
    by='room_type',
    figsize=(12, 8)
)

plt.title('Airbnb Prices by Room Type in Madrid')
plt.xlabel('Room Type')
plt.ylabel('Price (€)')
plt.xticks(rotation=30)
plt.show()

The pie chart shows that most of the Airbnb listings in this dataset are entire homes or apartments. I think this makes sense because many Airbnb hosts are homeowners who rent out their properties to travelers, and I believe this trend would also apply to Airbnb listings overseas. Another interesting factor is that many younger travelers in Europe, especially those in their twenties, often choose to stay in hostels because they are more affordable and provide opportunities to meet other travelers. Because of this, I think many of the entire home and apartment listings in Madrid could be targeted toward families or larger groups traveling together who want more privacy and space. These accommodations may also be popular among tourists visiting Madrid for longer periods of time. Overall, the pie chart shows that entire homes and apartments make up the majority of Airbnb listings in Madrid, suggesting that travelers looking for a private place to stay have more options than those looking for shared accommodations.

The boxplot shows some limitations in the dataset because there are significantly more listings for entire homes and apartments compared to hotel rooms and shared rooms. We can see that there are many outliers in the entire home and apartment category, as well as in private rooms. This could be because these categories have a much larger number of listings and a wider range of prices. On the other hand, hotel rooms and shared rooms have much smaller sample sizes, which makes their median prices less representative of the overall Airbnb market in Madrid. Because of the differences in the number of listings between each room type, it can be difficult to make accurate comparisons between the categories. Overall, the boxplot is useful for showing the differences in pricing and identifying outliers, but the uneven distribution of listings should be considered when analyzing the results.

Conclusion

Overall, this project helped me better understand the Airbnb market in Madrid, Spain, and how different factors such as location, price, room type, and number of reviews affect the listings throughout the city. From analyzing the dataset and creating different visualizations, I was able to identify several interesting trends in the Airbnb market.

One of the biggest findings from this project was that the Centro district has by far the largest concentration of Airbnb listings compared to the other districts in Madrid. This makes sense because Centro is one of the most popular tourist areas in the city, with major attractions such as Puerta del Sol and Plaza Mayor nearby. Another important finding was the difference in average Airbnb prices between districts. Salamanca had the highest average price, while Vicálvaro had the lowest. This shows how much the location of an Airbnb can affect its price and how tourists could save money by staying in districts outside the more expensive areas.

Another interesting finding was that entire homes and apartments make up the majority of Airbnb listings in Madrid, followed by private rooms. This could suggest that many Airbnb properties are targeted toward families and groups traveling together rather than individuals looking for shared accommodations. I also noticed that the number of reviews a listing receives does not necessarily mean it will have a higher price. There were many listings with fewer reviews that had higher prices than listings with hundreds of reviews.

There were also some limitations when analyzing the dataset, especially when looking at the boxplots. Since there were significantly more entire homes and apartments than hotel rooms and shared rooms, it was difficult to make accurate comparisons between some of the categories. There were also many outliers in the pricing data that could affect the averages.