探索資料集
在我們探討資料的視覺化之前,讓我們先來快速的瀏覽一下我們將要處理的資料集。我們將要使用的數據來自 openlights。我們將要使用航線資料集、機場資料集、航空公司資料集。其中,路徑資料的每一行對應的是兩個機場之間的飛行路徑;機場資料的每一行對應的是世界上的某一個機場,並且給出了相關資訊;航空公司的資料的每一行給出的是每一個航空公司。
首先我們先讀取資料:
# Import the pandas library. import pandas # Read in the airports data. airports = pandas.read_csv("airports.csv", header=None, dtype=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=None, dtype=str) airlines.columns = ["id", "name", "alias", "iata", "icao", "callsign", "country", "active"] # Read in the routes data. routes = pandas.read_csv("routes.csv", header=None, dtype=str) routes.columns = ["airline", "airline_id", "source", "source_id", "dest", "dest_id", "codeshare", "stops", "equipment"]
這些資料沒有列的***項,因此我們透過賦值column 屬性來新增列的***項。我們想要將每一列作為字串進行讀取,因為這樣做可以簡化後續以行 id 為匹配,對不同的資料框架進行比較的步驟。我們在讀取資料時設定了 dtype 屬性值達到此目的。
那麼在此之前我們需要做一些資料清洗的工作。
routes = routes[routes["airline_id"] != "//N"]
這一行指令就確保了我們在 airline_id 這一列只含有數值型資料。
製作長條圖
現在我們理解了資料的結構,我們可以進一步地開始描點來繼續探索這個問題。首先,我們將要使用 matplotlib 這個工具,matplotlib 是一個相對底層的 Python 堆疊中的描點函式庫,所以它比其他的工具庫要多敲一些指令來做出一個好看的曲線。另外一方面,你可以使用 matplotlib 幾乎做出任何的曲線,這是因為它十分的靈活,而靈活的代價就是非常難於使用。
我們首先透過做出一個長條圖來顯示不同的航空公司的航線長度分佈。一個長條圖將所有的航線的長度分割到不同的值域,然後對落入不同的值域範圍內的航線進行計數。從中我們可以知道哪些航空公司的航線長,哪些航空公司的航線短。
為了達到這一點,我們需要先計算航線的長度,***步就要使用距離公式,我們將會使用餘弦半正矢距離公式來計算經緯度刻畫的兩個點之間的距離。
import math def haversine(lon1, lat1, lon2, lat2): # Convert coordinates to floats. lon1, lat1, lon2, lat2 = [float(lon1), float(lat1), float(lon2), float(lat2)] # Convert to radians from degrees. lon1, lat1, lon2, lat2 = map(math.radians, [lon1, lat1, lon2, lat2]) # Compute distance. dlon = lon2 - lon1 dlat = lat2 - lat1 a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2 c = 2 * math.asin(math.sqrt(a)) km = 6367 * c return km
然後我們就可以使用一個函數來計算起點機場和終點機場之間的單程距離。我們需要從路線資料框架得到機場資料框架所對應的source_id 和dest_id,然後與機場的資料集的id 列相匹配,然後就只要計算就行了,這個函數是這樣的:
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 (ValueError, IndexError): pass return dist
如果source_id 和dest_id 欄位沒有有效值的話,那麼這個函數會報錯。因此我們需要增加 try/catch 模組對這種無效的情況進行捕捉。
***,我們將要使用 pandas 來將距離計算的函數運用到 routes 資料框架。這將會使我們得到包含所有的航線長度的 pandas 序列,其中航線線的長度都是以公里做單位。
route_lengths = routes.apply(calc_dist, axis=1)
現在我們就有了航線距離的序列了,我們將會建立一個長條圖,它將將資料歸類到對應的範圍之內,然後計數分別有多少的航線落入到不同的每個範圍:
import matplotlib.pyplot as plt %matplotlib inline plt.hist(route_lengths, bins=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 函數來描繪一個長條圖。一個核心的密度期望是一個曲線 —— 本質上是一個比柱狀圖平滑一點的,更容易看出其中的規律的曲線。
import seaborn seaborn.distplot(route_lengths, bins=20)
seaborn 同時有著更加好看的預設風格。 seaborn 不含有與每個 matplotlib 的版本相對應的版本,但是它的確是一個很好的快速描點工具,而且相比於 matplotlib 的預設圖表可以更好的幫助我們理解數據背後的含義。如果你想更深入的做一些統計方面的工作的話,seaborn 也不失為一個很好的庫。
長條圖
柱状图也虽然很好,但是有时候我们会需要航空公司的平均路线长度。这时候我们可以使用条形图--每条航线都会有一个单独的状态条,显示航空公司航线 的平均长度。从中我们可以看出哪家是国内航空公司哪家是国际航空公司。我们可以使用pandas,一个python的数据分析库,来酸楚每个航空公司的平 均航线长度。
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把结果画出来。
plt.bar(range(airline_route_lengths.shape[0]), airline_route_lengths["length"])
Matplotlib的plt.bar方法根据每个数据模型的航空公司平均航线长度(airline_route_lengths["length"])来做图。
问题是我们想看出哪家航空公司拥有的航线长度是什么并不容易。为了解决这个问题,我们需要能够看到坐标轴标签。这有点难,毕竟有这么多的航空公司。 一个能使问题变得简单的方法是使图表具有交互性,这样能实现放大跟缩小来查看轴标签。我们可以使用bokeh库来实现这个--它能便捷的实现交互性,作出 可缩放的图表。
要使用booked,我们需要先对数据进行预处理:
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 (ValueError, IndexError): 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_name, axis=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 无法正常运行。
现在,我们可以继续说图表问题:
import numpy as np from bokeh.io import output_notebook from bokeh.charts import Bar, show output_notebook() p = Bar(airline_route_lengths, 'name', values='length', title="Average airline route lengths") show(p)
用 output_notebook 创建背景虚化,在 iPython 的 notebook 里画出图。然后,使用数据帧和特定序列制作条形图。***,显示功能会显示出该图。
这个图实际上不是一个图像--它是一个 JavaScript 插件。因此,我们在下面展示的是一幅屏幕截图,而不是真实的表格。
有了它,我们可以放大,看哪一趟航班的飞行路线最长。上面的图像让这些表格看起来挤在了一起,但放大以后,看起来就方便多了。
水平条形图
Pygal 是一个能快速制作出有吸引力表格的数据分析库。我们可以用它来按长度分解路由。首先把我们的路由分成短、中、长三个距离,并在 route_lengths 里计算出它们各占的百分比。
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 的水平条形图里把每一个都绘成条形图:
import pygal from IPython.display import SVG chart = pygal.HorizontalBar() chart.title = 'Long, medium, and short routes' chart.add('Long', long_routes * 100) chart.add('Medium', medium_routes * 100) chart.add('Short', short_routes * 100) chart.render_to_file('routes.svg') SVG(filename='routes.svg')
首先,我们使用 pandasapplymethod 计算每个名称的长度。它将找到每个航空公司的名字字符的数量。然后,我们使用 matplotlib 做一个散点图来比较航空 id 的长度。当我们绘制时,我们把 theidcolumn of airlines 转换为整数类型。如果我们不这样做是行不通的,因为它需要在 x 轴上的数值。我们可以看到不少的长名字都出现在早先的 id 中。这可能意味着航空公司在成立前往往有较长的名字。
我们可以使用 seaborn 验证这个直觉。Seaborn 增强版的散点图,一个联合的点,它显示了两个变量是相关的,并有着类似地分布。
data = pandas.DataFrame({"lengths": name_lengths, "ids": airlines["id"].astype(int)})
seaborn.jointplot(x="ids", y="lengths", data=data)
画弧线
在地图上看到所有的航空路线是很酷的,幸运的是,我们可以使用 basemap 来做这件事。我们将画弧线连接所有的机场出发地和目的地。每个弧线想展示一个段都航线的路径。不幸的是,展示所有的线路又有太多的路由,这将会是一团糟。 替代,我们只现实前 3000 个路由。
# 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 库来做这个功能。
首先,计算机场之间连线的权重。
# 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 的连线。所以任何机场有两个或者更多连接的路由将会显示出来。
# Import networkx and initialize the graph. import networkx as nx graph = 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): pass pos=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的視覺化工具的詳細內容。更多資訊請關注PHP中文網其他相關文章!