Python 數據圖表工具的比較


Python 的科學棧相當成熟,各種應用場景都有相關的模塊,包括機器學習數據分析。數據可視化是發現數據和展示結果的重要一環,只不過過去以來,相對於 R 這樣的工具,發展還是落后一些。

幸運的是,過去幾年出現了很多新的Python數據可視化庫,彌補了一些這方面的差距。matplotlib 已經成為事實上的數據可視化方面最主要的庫,此外還有很多其他庫,例如vispy,bokeh, seaborn,  pyga, folium 和 networkx,這些庫有些是構建在 matplotlib 之上,還有些有其他一些功能。

本文會基於一份真實的數據,使用這些庫來對數據進行可視化。通過這些對比,我們期望了解每個庫所適用的范圍,以及如何更好的利用整個 Python 的數據可視化的生態系統。

我們在 Dataquest 建了一個交互課程,教你如何使用 Python 的數據可視化工具。如果你打算深入學習,可以點這里

探索數據集

在我們探討數據的可視化之前,讓我們先來快速的瀏覽一下我們將要處理的數據集。我們將要使用的數據來自 openflights。我們將要使用航線數據集機場數據集航空公司數據集。其中,路徑數據的每一行對應的是兩個機場之間的飛行路徑;機場數據的每一行對應的是世界上的某一個機場,並且給出了相關信息;航空公司的數據的每一行給出的是每一個航空公司。

首先我們先讀取數據:

  
1234567891011 # Import the pandas library.import pandas# Read in the airports data.airports pandas.read_csv("airports.csv"header=Nonedtype=str)airports.columns ["id""name""city""country""code""icao""latitude""longitude""altitude""offset""dst""timezone"]# Read in the airlines data.airlines pandas.read_csv("airlines.csv"header=Nonedtype=str)airlines.columns ["id""name""alias""iata""icao""callsign""country""active"]# Read in the routes data.routes pandas.read_csv("routes.csv"header=Nonedtype=str)routes.columns ["airline""airline_id""source""source_id""dest""dest_id""codeshare""stops""equipment"]

這些數據沒有列的首選項,因此我們通過賦值 column 屬性來添加列的首選項。我們想要將每一列作為字符串進行讀取,因為這樣做可以簡化后續以行 id 為匹配,對不同的數據框架進行比較的步驟。我們在讀取數據時設置了 dtype 屬性值達到這一目的。

我們可以快速瀏覽一下每一個數據集的數據框架。

  
1 airports.head()

     Python 
1 airlines.head()

     Python 
1 routes.head()

我們可以分別對每一個單獨的數據集做許多不同有趣的探索,但是只要將它們結合起來分析才能取得最大的收獲。Pandas 將會幫助我們分析數據,因為它能夠有效的過濾權值或者通過它來應用一些函數。我們將會深入幾個有趣的權值因子,比如分析航空公司和航線。

那么在此之前我們需要做一些數據清洗的工作。

  
1 routes routes[routes["airline_id"!= "\\N"]

這一行命令就確保了我們在 airline_id 這一列只含有數值型數據。

制作柱狀圖

現在我們理解了數據的結構,我們可以進一步地開始描點來繼續探索這個問題。首先,我們將要使用 matplotlib 這個工具,matplotlib 是一個相對底層的 Python 棧中的描點庫,所以它比其他的工具庫要多敲一些命令來做出一個好看的曲線。另外一方面,你可以使用 matplotlib 幾乎做出任何的曲線,這是因為它十分的靈活,而靈活的代價就是非常難於使用。

我們首先通過做出一個柱狀圖來顯示不同的航空公司的航線長度分布。一個柱狀圖將所有的航線的長度分割到不同的值域,然后對落入到不同的值域范圍內的航線進行計數。從中我們可以知道哪些航空公司的航線長,哪些航空公司的航線短。

為了達到這一點,我們需要首先計算一下航線的長度,第一步就要使用距離公式,我們將會使用余弦半正矢距離公式來計算經緯度刻畫的兩個點之間的距離。

  
12345678910111213 import mathdef haversine(lon1lat1lon2lat2):    # Convert coordinates to floats.    lon1lat1lon2lat2 [float(lon1)float(lat1)float(lon2)float(lat2)]    # Convert to radians from degrees.    lon1lat1lon2lat2 map(math.radians[lon1lat1lon2lat2])    # Compute distance.    dlon lon2 lon1     dlat lat2 lat1     math.sin(dlat/2)**math.cos(lat1math.cos(lat2math.sin(dlon/2)**2    math.asin(math.sqrt(a)    km 6367 c    return km

然后我們就可以使用一個函數來計算起點機場和終點機場之間的單程距離。我們需要從路線數據框架得到機場數據框架所對應的 source_id 和 dest_id,然后與機場的數據集的 id 列相匹配,然后就只要計算就行了,這個函數是這樣的:

  
1234567891011 def calc_dist(row):    dist 0    try:        # Match source and destination to get coordinates.        source airports[airports["id"== row["source_id"]].iloc[0]        dest airports[airports["id"== row["dest_id"]].iloc[0]        # Use coordinates to compute distance.        dist haversine(dest["longitude"]dest["latitude"]source["longitude"]source["latitude"])    except (ValueErrorIndexError):        pass    return dist

如果 source_id 和 dest_id 列沒有有效值的話,那么這個函數會報錯。因此我們需要增加 try/catch 模塊對這種無效的情況進行捕捉。

最后,我們將要使用 pandas 來將距離計算的函數運用到 routes 數據框架。這將會使我們得到包含所有的航線線長度的 pandas 序列,其中航線線的長度都是以公里做單位。

  
1 route_lengths routes.apply(calc_distaxis=1)

現在我們就有了航線距離的序列了,我們將會創建一個柱狀圖,它將會將數據歸類到對應的范圍之內,然后計數分別有多少的航線落入到不同的每個范圍:

  
12345 import matplotlib.pyplot as plt %matplotlib inline   plt.hist(route_lengthsbins=20)

我們用 import matplotlib.pyplot as plt 導入 matplotlib 描點函數。然后我們就使用 %matplotlib inline 來設置 matplotlib 在 ipython 的 notebook 中描點,最終我們就利用 plt.hist(route_lengths, bins=20) 得到了一個柱狀圖。正如我們看到的,航空公司傾向於運行近距離的短程航線,而不是遠距離的遠程航線。

使用 seaborn

我們可以利用 seaborn 來做類似的描點,seaborn 是一個 Python 的高級庫。Seaborn 建立在 matplotlib 的基礎之上,做一些類型的描點,這些工作常常與簡單的統計工作有關。我們可以基於一個核心的概率密度的期望,使用 distplot 函數來描繪一個柱狀圖。一個核心的密度期望是一個曲線 —— 本質上是一個比柱狀圖平滑一點的,更容易看出其中的規律的曲線。

  
12 import seaborn seaborn.distplot(route_lengthsbins=20)

正如你所看到的那樣,seaborn 同時有着更加好看的默認風格。seaborn 不含有與每個 matplotlib 的版本相對應的版本,但是它的確是一個很好的快速描點工具,而且相比於 matplotlib 的默認圖表可以更好的幫助我們理解數據背后的含義。如果你想更深入的做一些統計方面的工作的話,seaborn 也不失為一個很好的庫。

條形圖

柱狀圖也雖然很好,但是有時候我們會需要航空公司的平均路線長度。這時候我們可以使用條形圖--每條航線都會有一個單獨的狀態條,顯示航空公司航線的平均長度。從中我們可以看出哪家是國內航空公司哪家是國際航空公司。我們可以使用pandas,一個python的數據分析庫,來算出每個航空公司的平均航線長度。

  
1234567 import numpy# Put relevant columns into a dataframe.route_length_df pandas.DataFrame({"length"route_lengths"id"routes["airline_id"]})# Compute the mean route length per airline.airline_route_lengths route_length_df.groupby("id").aggregate(numpy.mean)# Sort by length so we can make a better chart.airline_route_lengths airline_route_lengths.sort("length"ascending=False)

我們首先用航線長度和航空公司的id來搭建一個新的數據框架。我們基於airline_id把route_length_df拆分成組,為每個航空公司建立一個大體的數據框架。然后我們調用pandas的aggregate函數來獲取航空公司數據框架中長度列的均值,然后把每個獲取到的值重組到一個新的數據模型里。之后把數據模型進行排序,這樣就使得擁有最多航線的航空公司拍到了前面。

這樣就可以使用matplotlib把結果畫出來。

  
1 plt.bar(range(airline_route_lengths.shape[0])airline_route_lengths["length"])

Matplotlib的plt.bar方法根據每個數據模型的航空公司平均航線長度(airline_route_lengths["length"])來做圖。

問題是我們想看出哪家航空公司擁有的航線長度是什么並不容易。為了解決這個問題,我們需要能夠看到坐標軸標簽。這有點難,畢竟有這么多的航空公司。一個能使問題變得簡單的方法是使圖表具有交互性,這樣能實現放大跟縮小來查看軸標簽。我們可以使用bokeh庫來實現這個--它能便捷的實現交互性,作出可縮放的圖表。

要使用booked,我們需要先對數據進行預處理:

  
12345678910111213 def lookup_name(row):    try:        # Match the row id to the id in the airlines dataframe so we can get the name.        name airlines["name"][airlines["id"== row["id"]].iloc[0]    except (ValueErrorIndexError):        name ""    return name# Add the index (the airline ids) as a column.airline_route_lengths["id"airline_route_lengths.index.copy()# Find all the airline names.airline_route_lengths["name"airline_route_lengths.apply(lookup_nameaxis=1)# Remove duplicate values in the index.airline_route_lengths.index range(airline_route_lengths.shape[0])

上面的代碼會獲取airline_route_lengths中每列的名字,然后添加到name列上,這里存貯着每個航空公司的名字。我們也添加到id列上以實現查找(apply函數不傳index)。

最后,我們重置索引序列以得到所有的特殊值。沒有這一步,Bokeh 無法正常運行。

現在,我們可以繼續說圖表問題:

  
123456 import numpy as npfrom bokeh.io import output_notebookfrom bokeh.charts import Barshowoutput_notebook()Bar(airline_route_lengths'name'values='length'title="Average airline route lengths")show(p)

用 output_notebook 創建背景虛化,在 iPython 的 notebook 里畫出圖。然后,使用數據幀和特定序列制作條形圖。最后,顯示功能會顯示出該圖。

這個圖實際上不是一個圖像--它是一個 JavaScript 插件。因此,我們在下面展示的是一幅屏幕截圖,而不是真實的表格。

有了它,我們可以放大,看哪一趟航班的飛行路線最長。上面的圖像讓這些表格看起來擠在了一起,但放大以后,看起來就方便多了。

水平條形圖

Pygal 是一個能快速制作出有吸引力表格的數據分析庫。我們可以用它來按長度分解路由。首先把我們的路由分成短、中、長三個距離,並在 route_lengths 里計算出它們各占的百分比。

      Python 
123 long_routes = len([k for k in route_lengths if k > 10000]) / len(route_lengths)medium_routes = len([k for k in route_lengths if k < 10000 and k > 2000]) / len(route_lengths)short_routes = len([k for k in route_lengths if k < 2000]) / len(route_lengths)

然后我們可以在 Pygal 的水平條形圖里把每一個都繪成條形圖:

首先,我們創建一個空圖。然后,我們添加元素,包括標題和條形圖。每個條形圖通過百分比值(最大值是100)顯示出該類路由的使用頻率。

最后,我們把圖表渲染成文件,用 IPython 的 SVG 功能載入並展示文件。這個圖看上去比默認的 matplotlib 圖好多了。但是為了制作出這個圖,我們要寫的代碼也多很多。因此,Pygal 可能比較適用於制作小型的展示用圖表。

散點圖

在散點圖里,我們能夠縱向比較數據。我們可以做一個簡單的散點圖來比較航空公司的 id 號和航空公司名稱的長度:

     Python 
12 name_lengths = airlines["name"].apply(lambda x: len(str(x)))plt.scatter(airlines["id"].astype(int), name_lengths)

首先,我們使用 pandasapplymethod 計算每個名稱的長度。它將找到每個航空公司的名字字符的數量。然后,我們使用 matplotlib 做一個散點圖來比較航空 id 的長度。當我們繪制時,我們把 theidcolumn of airlines 轉換為整數類型。如果我們不這樣做是行不通的,因為它需要在 x 軸上的數值。我們可以看到不少的長名字都出現在早先的 id 中。這可能意味着航空公司在成立前往往有較長的名字。

我們可以使用 seaborn 驗證這個直覺。Seaborn 增強版的散點圖,一個聯合的點,它顯示了兩個變量是相關的,並有着類似地分布。

      Python 
12 data = pandas.DataFrame({"lengths": name_lengths, "ids": airlines["id"].astype(int)})seaborn.jointplot(x="ids", y="lengths", data=data)

上面的圖表明,兩個變量之間的相關性是不明確的——r 的平方值是低的。

靜態 maps

我們的數據天然的適合繪圖-機場有經度和緯度對,對於出發和目的機場來說也是。

第一張圖做的是顯示全世界的所有機場。可以用擴展於 matplotlib 的 basemap 來做這個。這允許畫世界地圖和添加點,而且很容易定制。

      Python 
12345678910111213 # Import the basemap packagefrom mpl_toolkits.basemap import Basemap# Create a map on which to draw.  We're using a mercator projection, and showing the whole world.m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80,llcrnrlon=-180,urcrnrlon=180,lat_ts=20,resolution='c')# Draw coastlines, and the edges of the map.m.drawcoastlines()m.drawmapboundary()# Convert latitude and longitude to x and y coordinatesx, y = m(list(airports["longitude"].astype(float)), list(airports["latitude"].astype(float)))# Use matplotlib to draw the points onto the map.m.scatter(x,y,1,marker='o',color='red')# Show the plot.plt.show()

 

在上面的代碼中,首先用 mercator projection 畫一個世界地圖。墨卡托投影是將整個世界的繪圖投射到二位曲面。然后,在地圖上用紅點點畫機場。

上面地圖的問題是找到每個機場在哪是困難的-他們就是在機場密度高的區域合並城一團紅色斑點。

就像聚焦不清楚,有個交互制圖的庫,folium,可以進行放大地圖來幫助我們找到個別的機場。

      Python 
1234567891011 import folium# Get a basic world map.airports_map = folium.Map(location=[30, 0], zoom_start=2)# Draw markers on the map.for name, row in airports.iterrows():    # For some reason, this one airport causes issues with the map.    if row["name"] != "South Pole Station":        airports_map.circle_marker(location=[row["latitude"], row["longitude"]], popup=row["name"])# Create and show the map.airports_map.create_map('airports.html')airports_map

Folium 使用 leaflet.js 來制作全交互式地圖。你可以點擊每一個機場在彈出框中看名字。在上邊顯示一個截屏,但是實際的地圖更令人印象深刻。Folium 也允許非常廣闊的修改選項來做更好的標注,或者添加更多的東西到地圖上。

畫弧線

在地圖上看到所有的航空路線是很酷的,幸運的是,我們可以使用 basemap 來做這件事。我們將畫弧線連接所有的機場出發地和目的地。每個弧線想展示一個段都航線的路徑。不幸的是,展示所有的線路又有太多的路由,這將會是一團糟。替代,我們只現實前 3000 個路由。

      Python 
123456789101112131415161718 # Make a base map with a mercator projection.  Draw the coastlines.m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80,llcrnrlon=-180,urcrnrlon=180,lat_ts=20,resolution='c')m.drawcoastlines()# Iterate through the first 3000 rows.for name, row in routes[:3000].iterrows():    try:        # Get the source and dest airports.        source = airports[airports["id"] == row["source_id"]].iloc[0]        dest = airports[airports["id"] == row["dest_id"]].iloc[0]        # Don't draw overly long routes.        if abs(float(source["longitude"]) - float(dest["longitude"])) < 90:            # Draw a great circle between source and dest airports.            m.drawgreatcircle(float(source["longitude"]), float(source["latitude"]), float(dest["longitude"]), float(dest["latitude"]),linewidth=1,color='b')    except (ValueError, IndexError):        pass # Show the map.plt.show()

上面的代碼將會畫一個地圖,然后再在地圖上畫線路。我們添加一了寫過濾器來阻止過長的干擾其他路由的長路由。

畫網絡圖

我們將做的最終的探索是畫一個機場網絡圖。每個機場將會是網絡中的一個節點,並且如果兩點之間有路由將划出節點之間的連線。如果有多重路由,將添加線的權重,以顯示機場連接的更多。將使用 networkx 庫來做這個功能。

首先,計算機場之間連線的權重。

      Python 
1234567891011121314151617181920212223 # Initialize the weights dictionary.weights = {}# Keep track of keys that have been added once -- we only want edges with a weight of more than 1 to keep our network size manageable.added_keys = []# Iterate through each route.for name, row in routes.iterrows():    # Extract the source and dest airport ids.    source = row["source_id"]    dest = row["dest_id"]     # Create a key for the weights dictionary.    # This corresponds to one edge, and has the start and end of the route.    key = "{0}_{1}".format(source, dest)    # If the key is already in weights, increment the weight.    if key in weights:        weights[key] += 1    # If the key is in added keys, initialize the key in the weights dictionary, with a weight of 2.    elif key in added_keys:        weights[key] = 2    # If the key isn't in added_keys yet, append it.    # This ensures that we aren't adding edges with a weight of 1.    else:        added_keys.append(key)

一旦上面的代碼運行,這個權重字典就包含了每兩個機場之間權重大於或等於 2 的連線。所以任何機場有兩個或者更多連接的路由將會顯示出來。

      Python 
1234567891011121314151617181920212223242526272829303132 # Import networkx and initialize the graph.import networkx as nxgraph = nx.Graph()# Keep track of added nodes in this set so we don't add twice.nodes = set()# Iterate through each edge.for k, weight in weights.items():    try:        # Split the source and dest ids and convert to integers.        source, dest = k.split("_")        source, dest = [int(source), int(dest)]        # Add the source if it isn't in the nodes.        if source not in nodes:            graph.add_node(source)        # Add the dest if it isn't in the nodes.        if dest not in nodes:            graph.add_node(dest)        # Add both source and dest to the nodes set.        # Sets don't allow duplicates.        nodes.add(source)        nodes.add(dest)         # Add the edge to the graph.        graph.add_edge(source, dest, weight=weight)    except (ValueError, IndexError):        passpos=nx.spring_layout(graph)# Draw the nodes and edges.nx.draw_networkx_nodes(graph,pos, node_color='red', node_size=10, alpha=0.8)nx.draw_networkx_edges(graph,pos,width=1.0,alpha=1)# Show the plot.plt.show()

 

總結

有一個成長的數據可視化的 Python 庫,它可能會制作任意一種可視化。大多數庫基於 matplotlib 構建的並且確保一些用例更簡單。如果你想更深入的學習怎樣使用 matplotlib,seaborn 和其他工具來可視化數據,在這兒檢出其他課程。

 

FROM: http://python.jobbole.com/84218/


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com