{"id":725,"date":"2019-03-08T20:22:18","date_gmt":"2019-03-08T20:22:18","guid":{"rendered":"http:\/\/marcel-jan.eu\/datablog\/?p=725"},"modified":"2019-04-24T21:30:44","modified_gmt":"2019-04-24T21:30:44","slug":"showing-a-complex-excel-sheet-whos-boss-with-python-and-pandas","status":"publish","type":"post","link":"https:\/\/marcel-jan.eu\/datablog\/2019\/03\/08\/showing-a-complex-excel-sheet-whos-boss-with-python-and-pandas\/","title":{"rendered":"Showing a complex Excel sheet who&#8217;s boss with Python and pandas"},"content":{"rendered":"<p>Data engineering isn&#8217;t always creating serverless APIs and ingressing terrabyte a minute streams with do-hickeys on Kubernetes. Sometimes people just want their Excel sheet in the data lake. Is that big data? Not even close. It&#8217;s very small. But for some people it&#8217;s a first step in a data driven world.<\/p>\n<p>But does Hadoop read Excel? Not to my knowledge. But NiFi, that wonderful open source data flow software has an Excel processor. It can even help you to work the data a little. But some Excel sheets simply need too much reworking. And that&#8217;s simply too big a job for NiFi. I&#8217;ve used Python and the pandas library to create a csv file that Hadoop can handle.<\/p>\n<p><!--more--><\/p>\n<p>&nbsp;<\/p>\n<h2>Files<\/h2>\n<p>You can download the example files for this on GitHub:<\/p>\n<p><a href=\"https:\/\/github.com\/Marcel-Jan\/Fun-with-Python\/blob\/master\/Pandas%20moon%20fueling%20github.xlsx\">Pandas moon fueling github.xlsx<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/Marcel-Jan\/Fun-with-Python\/blob\/master\/pandas_moon_fueling.py\">pandas_moon_fueling.py<\/a><\/p>\n<p>The Excel sheet doesn&#8217;t have the exact amount of worksheets mentioned here, but the moon fuel stations sheet is identical.<\/p>\n<p>&nbsp;<\/p>\n<h2>In the left corner: the Excel sheet&#8230;<\/h2>\n<p>Clearly at my work we don&#8217;t maintain fueling stations on the Moon (yet), but the Excel sheet I encountered looked sort of like the layout of this one. Let&#8217;s take a look.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-741\" src=\"https:\/\/marcel-jan.eu\/datablog\/wp-content\/uploads\/2019\/03\/moonfueling-1-1024x350.jpg\" alt=\"\" width=\"640\" height=\"219\" \/><\/p>\n<p>On the lefthand side you see three columns that define the fueling stations. The customers each have their own column for every week. There also is a summation of the data per week. Not seen here are the summations per year, per customer and totals per week. Every alternate row is either the amount of fuel and the money the customer paid.<\/p>\n<p>I could save this as CSV file and store it in Hadoop, but it would give any data scientist a head ache. It&#8217;s more or less understandable for the human eye, but software won&#8217;t get this.<\/p>\n<p>I think that this layout below would be much easier to use in analytics software:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-728\" src=\"https:\/\/marcel-jan.eu\/datablog\/wp-content\/uploads\/2019\/03\/moonfuelingcsv.jpg\" alt=\"\" width=\"903\" height=\"439\" \/><\/p>\n<p>But how do we accomplish that?<\/p>\n<p>&nbsp;<\/p>\n<h2>In the right corner: pandas!<\/h2>\n<p>Guess what? Python&#8217;s pandas library can read Excel. It even has a read_excel function. It&#8217;s almost as if other people got data delivered in Excel format..<\/p>\n<p>Let&#8217;s start by importing pandas.<\/p>\n<pre>import pandas as pd<\/pre>\n<p>In my terminal window I have room enough to make my pandas output a little wider.<\/p>\n<pre># Show wider tables. I've got the screen for it.\r\npd.set_option('display.width', 300)<\/pre>\n<p>Now open the Excel file and let&#8217;s show the sheets in it.<\/p>\n<pre>fuel_file = \"Pandas moon fueling.xlsx\"\r\nxl = pd.ExcelFile(fuel_file)\r\nprint(xl.sheet_names)<\/pre>\n<p>Because we have fueling stations all over the solar system (as you do), this results in:<\/p>\n<pre>['Moon', 'Eros', 'Ceres', 'Mars', 'Europa', 'Ganymedes', 'Enceladus']<\/pre>\n<p>But in this case I just want to start by reading the Moon data. Now pandas has read_excel, but you can also use xl.parse. xl being where I stored my Excel data previously. It&#8217;s basically the same. I just found this on StackOverflow first \ud83d\ude42 .<\/p>\n<p>&nbsp;<\/p>\n<h2>Headers and indexes<\/h2>\n<p>But in this case that simple example you&#8217;ll find first when googling &#8220;pandas read excel&#8221; is not enough. This has to do with headers and indexes. If you don&#8217;t pick your headers and indexes from the Excel sheet, pandas will pick something.<\/p>\n<p>I&#8217;ve been reading everywhere to learn how to choose these. Because normally you have just a bunch of column names in a row. That would be your header row. Not so now. We have multiple levels of rows.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-732\" src=\"https:\/\/marcel-jan.eu\/datablog\/wp-content\/uploads\/2019\/03\/moonfueling_headind-1024x408.jpg\" alt=\"\" width=\"640\" height=\"255\" \/><\/p>\n<p>So this is how you read the Moon sheet and store it in a dataframe.<\/p>\n<pre>fuel_df = xl.parse(\"Moon\", header=[1], index_col=[0,1,2])<\/pre>\n<p>I chose the row that has the weeknumbers: row 2 in the screenshot above. And counting from zero, that would be row 1. Therefor, header=[1].<\/p>\n<p>The index is basically your row name. Database people might think of this one like a primary key. In this case I wanted the first three columns combined. Therefor, index_col=[0,1,2].<\/p>\n<p>Let&#8217;s have a look at what we got now:<\/p>\n<pre>print(fuel_df.head())<\/pre>\n<pre>                                                     Unnamed: 3   Unnamed: 4 Unnamed: 5 Week 01  \r\nLocation  Description                   Fueling port        NaN          NaN        NaN   Total  \r\n                                        Fueling port     SpaceX  Blue Origin  Landspace     NaN  \r\nCOPS-001O Copernicus Station 1 Oxygen   C003              97916          NaN          0   97916  \r\n                                        C003             133686          NaN     1012.5  134698  \r\nCOPS-001H Copernicus Station 1 Hydrogen C003             195040          NaN        NaN  195040\r\n[..]<\/pre>\n<p>The first row with the &#8220;Unnamed: 3 \u00a0 Unnamed: 4\u00a0 &#8221; is now our header. You can see the weeknumber in there and that&#8217;s what I was going for.<\/p>\n<p>You can see the complete header with this:<\/p>\n<pre>print(fuel_df.columns)<\/pre>\n<pre>Index(['Unnamed: 3', 'Unnamed: 4', 'Unnamed: 5', 'Week 01', 'Unnamed: 7', 'Unnamed: 8', 'Unnamed: 9', 'Unnamed: 10', 'Week 02', 'Unnamed: 12', 'Unnamed: 13', 'Week 03', 'Unnamed: 15', 'Unnamed: 16', 'Unnamed: 17', 'Week 04'], dtype='object')<\/pre>\n<p>Clearly this is not sufficient. Let&#8217;s work on that.<\/p>\n<p>&nbsp;<\/p>\n<h2>Weeknumbers in the header<\/h2>\n<p>One weakness in the Excel sheet, is that weeknumbers are to the right of the data from that week. Uninformed software would assume the data of that week is in the columns right of it. What I want, is that every column of a certain weeknumber get that weeknumber in the header.<\/p>\n<p>I start by storing the column names (the header) in a variable and work on that. I also store the row with the customer names. We&#8217;ll get to that later.<\/p>\n<pre>weeknumbers_list = list(fuel_df.columns)\r\nspacecustomers = fuel_df.iloc[1]<\/pre>\n<p>My way of doing this might be more cumbersome than necessary, but it does the job.<\/p>\n<pre>start_position = 0\r\n# Loop through values with \"Week\" in the name\r\nfor weeknr_pos in positions_weeknumbers:\r\n    # Go from the start position of that week and name everything with that weeknumber\r\n    # until the end of that week.\r\n    for pos in range(start_position, weeknr_pos):\r\n        # Cell gets weeknumber in the \r\n        weeknumbers_list[pos] = weeknumbers_list[weeknr_pos]\r\n    # Determine the first column position of the next weeknumber.\r\n    start_position = weeknr_pos + 1\r\n    # The last column of weeknr has the totals. We'll remove that one.\r\n    weeknumbers_list[weeknr_pos] = \"Remove\"<\/pre>\n<p>Last step: feed the weeknumber list to the dataframe header.<\/p>\n<pre>fuel_df.columns = weeknumbers_list<\/pre>\n<p>&nbsp;<\/p>\n<h2>Remove the unnecessary stuff<\/h2>\n<p>Next, let&#8217;s remove the first two rows and let&#8217;s remove all columns with Total in the name. We already indicated them with the term &#8220;Remove&#8221;.<\/p>\n<pre>fuel_df = fuel_df.iloc[2:]<\/pre>\n<pre>fuel_df.drop(\"Remove\", axis=1, inplace=True)<\/pre>\n<p>So what do we got now?<\/p>\n<pre>                                             Week 01 Week 01 Week 01 Week 02 Week 02\r\nCOPS-001O Copernicus Station 1 Oxygen   C003   97916     NaN       0     NaN     NaN\r\n                                        C003  133686     NaN  1012.5     NaN     NaN\r\nCOPS-001H Copernicus Station 1 Hydrogen C003  195040     NaN     NaN     NaN     NaN\r\n                                        C003   90371     NaN     NaN     NaN     NaN\r\nMRUM-003  Mons R\u00fcmker Base 2            B12      NaN     NaN     NaN     NaN     NaN\r\n[..]<\/pre>\n<p>&nbsp;<\/p>\n<h2>Multiple headers<\/h2>\n<p>We&#8217;re missing something, aren&#8217;t we? We&#8217;re missing the names of the customers. But I have prepared something for that. There&#8217;s a spacecustomers variable with all the names in them. But it also has some of those NaN values in them. So let&#8217;s clean that up.<\/p>\n<pre>spacecustomers_cleanlist = [x for x in spacecustomers if str(x) != 'nan']<\/pre>\n<p>Now I want to add the customer list to the header as an extra layer. Why? Because I want those columns not just identified by weeknumbers, but also by customer names.<\/p>\n<p>I&#8217;ve been looking for SO long on how to do this. I&#8217;ve been trying and trying and this is what I came up with. Use this to your advantage.<\/p>\n<pre>column_array = []\r\ncolumn_array.append(fuel_df.columns)\r\ncolumn_array.append(spacecustomers_cleanlist)\r\n<\/pre>\n<p>column_array here is basically a list of lists. Maybe I shouldn&#8217;t have called it an array. Too confusing. Anyway, this is a way to feed two lists as two headers in the fuel_df dataframe.<\/p>\n<pre>fuel_df.columns = column_array<\/pre>\n<p>Now our dataframe looks like this<\/p>\n<pre>                                                     Week 01                        Week 02\r\n                                                      SpaceX Blue Origin Landspace   SpaceX\r\nCOPS-001O Copernicus Station 1 Oxygen    C003          97916         NaN         0      NaN\r\n                                         C003         133686         NaN    1012.5      NaN\r\nCOPS-001H Copernicus Station 1 Hydrogen  C003         195040         NaN       NaN      NaN\r\n                                         C003          90371         NaN       NaN      NaN\r\nMRUM-003  Mons R\u00fcmker Base 2             B12             NaN         NaN       NaN      NaN\r\n                                         B12             NaN         NaN       NaN      NaN\r\n[..]<\/pre>\n<p>I&#8217;m so happy that I managed to do this. I&#8217;ve been reading about pandas&#8217; MultiIndex feature, and all kinds of crazy schemes and possible solutions. It would not work. Until I saw this video by Zax Rosenberg speaking at a Chicaco Python Users Group meeting about just MultiIndexes and related features. Then I understood what I needed to do.<\/p>\n<p><iframe loading=\"lazy\" title=\"Pandas MultiIndex Tutorial and Best Practices\" width=\"750\" height=\"422\" src=\"https:\/\/www.youtube.com\/embed\/kP-0ET0V5Tc?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>&nbsp;<\/p>\n<h2>How to tell what row is what?<\/h2>\n<p>There&#8217;s still the little matter of telling which row has amounts of fuel and which one has prices in our dataframe. For this we create a list and add this as a new column. I feel I could have done this part a little smarter, but this for now worked. I just tell that even rownumbers are amounts and uneven rownumbers are the prices.<\/p>\n<pre># Add a column to define what row is the amount of fuel and what is the price\r\nrow_definer_column = []\r\nfor rowdef in range(2, sheet_depth):\r\n    # print(rowdef)\r\n    if rowdef % 2 == 0:\r\n        # Even\r\n        row_definer_column.append(\"Amount\")\r\n    elif rowdef % 2 == 1 and rowdef != 1:\r\n        # Uneven\r\n        row_definer_column.append(\"Price\")<\/pre>\n<p>And this is how we add this list as the fourth column (loc=3). This only works if your list is as long as the rest of the data.<\/p>\n<pre>fuel_df.insert(loc=3, column='amount_or_price', value=row_definer_column)<\/pre>\n<p>We also add this to our already existing MultiIndex by appending it (thanks, Zax Rosenberg!).<\/p>\n<pre>fuel_df.set_index('amount_or_price', append=True, inplace=True)<\/pre>\n<p>The names of the other columns in the MultiIndex were missing, so let&#8217;s fix that now:<\/p>\n<pre>fuel_df.index.names = ['Location', 'Description', 'Fueling_port', 'amount_or_price']<\/pre>\n<p>Let&#8217;s take another look at our dataframe. Where are we now?<\/p>\n<pre>                                                                     Week 01                       Week 02\r\n                                                                      SpaceX Blue Origin Landspace  SpaceX\r\nLocation  Description                   Fueling_port amount_or_price                                      \r\nCOPS-001O Copernicus Station 1 Oxygen   C003         Amount            97916         NaN         0     NaN\r\n                                                     Price            133686         NaN    1012.5     NaN\r\nCOPS-001H Copernicus Station 1 Hydrogen C003         Amount           195040         NaN       NaN     NaN\r\n                                                     Price             90371         NaN       NaN     NaN\r\nMRUM-003  Mons R\u00fcmker Base 2            B12          Amount              NaN         NaN       NaN     NaN<\/pre>\n<p>Not shown in this are a couple of trailing rows with subtotals and totals. We don&#8217;t need that. It will mess up the results. We have analytics software for making all those wonderful reports with those totals and subtotals.<\/p>\n<pre>fuel_df = fuel_df.iloc[:-6]\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h2>And now: our magic stacking trick!<\/h2>\n<p>I hope you made it up to here, because this is where the beautiful part starts. Getting all that data in 1-dimensional rows is now so easy, you won&#8217;t believe it. We spend all that time defining headers and indexes. This is where it all will pay off.<\/p>\n<p>First, let&#8217;s show how our MultiIndex looks now:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-736\" src=\"https:\/\/marcel-jan.eu\/datablog\/wp-content\/uploads\/2019\/03\/moonfueling_multiindex-1024x334.jpg\" alt=\"\" width=\"640\" height=\"209\" \/><\/p>\n<p>We&#8217;re going to start by saying that we want the customer data no longer as a header. We want to stack the data as part of the index.What that means, is that we get a new column with customer names and all the data corresponding to that pivots accordingly.<\/p>\n<p>And we do this with one simple command. Our customer data is in the header, level 1. We stack our data based on that:<\/p>\n<pre>fuel_df = fuel_df.stack(1)<\/pre>\n<p>If you don&#8217;t get what I&#8217;m saying here, here&#8217;s the result.<\/p>\n<pre>                                                                                        Week 01 Week 02  Week 03 Week 04\r\nLocation  Description                   Fueling_port amount_or_price\r\nCOPS-001O Copernicus Station 1 Oxygen   C003         Amount          Landspace                0    3000      NaN     NaN\r\n                                                                     SpaceX               97916     NaN      NaN     NaN\r\n                                                                     Virgin Galactic        NaN     992      NaN   34293\r\n                                                     Price           Landspace           1012.5    6750      NaN     NaN\r\n                                                                     SpaceX              133686     NaN      NaN     NaN\r\n                                                                     Virgin Galactic        NaN  2012.5      NaN   62300\r\nCOPS-001H Copernicus Station 1 Hydrogen C003         Amount          Landspace              NaN    3000      NaN     NaN\r\n                                                                     SpaceX              195040     NaN      NaN     NaN\r\n                                                                     Virgin Galactic        NaN    1552      NaN     NaN\r\n                                                     Price           Landspace              NaN    9000      NaN     NaN\r\n                                                                     SpaceX               90371     NaN      NaN     NaN\r\n                                                                     Virgin Galactic        NaN    2107      NaN     NaN\r\nMRUM-003  Mons R\u00fcmker Base 2            B12          Amount          Landspace              NaN     NaN      NaN     430\r\n                                                     Price           Landspace              NaN     NaN      NaN     943<\/pre>\n<p>Notice the new column on the right of amount_or_price. It doesn&#8217;t have a name yet. We do that in a moment.<\/p>\n<p>First we want the amount and price data to move to the header. Again, with the MultiIndex this is so simple. The amount_or_price column in the index is now on position 4 (or 3, counting from 0).<\/p>\n<pre>fuel_df = fuel_df.unstack(3)<\/pre>\n<p>And let&#8217;s have a look at the result:<\/p>\n<pre>                                                                       Week 01          Week 02          Week 03        Week 04\r\namount_or_price                                                         Amount    Price  Amount   Price   Amount  Price  Amount   Price\r\nLocation  Description                   Fueling_port\r\nCOPS-001O Copernicus Station 1 Oxygen   C003         Landspace               0   1012.5    3000    6750      NaN    NaN     NaN     NaN\r\n                                                     SpaceX              97916   133686     NaN     NaN      NaN    NaN     NaN     NaN\r\n                                                     Virgin Galactic       NaN      NaN     992  2012.5      NaN    NaN   34293   62300\r\nCOPS-001H Copernicus Station 1 Hydrogen C003         Landspace             NaN      NaN    3000    9000      NaN    NaN     NaN     NaN\r\n                                                     SpaceX             195040    90371     NaN     NaN      NaN    NaN     NaN     NaN\r\n                                                     Virgin Galactic       NaN      NaN    1552    2107      NaN    NaN     NaN     NaN\r\nMRUM-003  Mons R\u00fcmker Base 2            B12          Landspace             NaN      NaN     NaN     NaN      NaN    NaN     430     94<\/pre>\n<p>Now we have a two level header again, with weeknumbers and amount\/prices. One more step to go. The weeknumbers must become a new column. We need to use stack again and for this we look at the level in the header again. Weeknumbers are level 0 in the header.<\/p>\n<pre>fuel_df = fuel_df.stack(0)<\/pre>\n<p>Which gives us all that data in nice one dimensional rows.<\/p>\n<pre>amount_or_price                                                                  Amount    Price\r\nLocation  Description                   Fueling_port\r\nCOPS-001O Copernicus Station 1 Oxygen   C003         Landspace         Week 01        0   1012.5\r\n                                                                       Week 02     3000     6750\r\n                                                     SpaceX            Week 01    97916   133686\r\n                                                     Virgin Galactic   Week 02      992   2012.5\r\n                                                                       Week 04    34293    62300\r\nCOPS-001H Copernicus Station 1 Hydrogen C003         Landspace         Week 02     3000     9000\r\n                                                     SpaceX            Week 01   195040    90371\r\n                                                     Virgin Galactic   Week 02     1552     2107\r\nMRUM-003  Mons R\u00fcmker Base 2            B12          Landspace         Week 04      430      943<\/pre>\n<p>Add nice index names for customer data and weeknumbers (I assure you space customer is a technical term).<\/p>\n<pre>fuel_df.index.names = ['Location', 'Description', 'Fueling_port', 'Space_customer', 'Weeknumber']<\/pre>\n<p>All that&#8217;s left to do, is write it to a csv file.<\/p>\n<pre>print(fuel_df.to_csv('Pandas moon fueling.csv'))<\/pre>\n<p>This is what it looks like:<\/p>\n<pre>Location,Description,Fueling_port,Space_customer,Weeknumber,Amount,Price\r\nCOPS-001O,Copernicus Station 1 Oxygen,C003,Landspace,Week 01,0,1012.5\r\nCOPS-001O,Copernicus Station 1 Oxygen,C003,Landspace,Week 02,3000,6750\r\nCOPS-001O,Copernicus Station 1 Oxygen,C003,SpaceX,Week 01,97916,133685.5\r\nCOPS-001O,Copernicus Station 1 Oxygen,C003,Virgin Galactic,Week 02,992,2012.5\r\nCOPS-001O,Copernicus Station 1 Oxygen,C003,Virgin Galactic,Week 04,34293,62300\r\nCOPS-001H,Copernicus Station 1 Hydrogen,C003,Landspace,Week 02,3000,9000\r\nCOPS-001H,Copernicus Station 1 Hydrogen,C003,SpaceX,Week 01,195040,90371\r\nCOPS-001H,Copernicus Station 1 Hydrogen,C003,Virgin Galactic,Week 02,1552,2107\r\nMRUM-003,Mons R\u00fcmker Base 2,B12,Landspace,Week 04,430,943\r\n[..]<\/pre>\n<p>Who&#8217;s unstructured data now, huh? Victory is mine!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Data engineering isn&#8217;t always creating serverless APIs and ingressing terrabyte a minute streams with do-hickeys on Kubernetes. Sometimes people just want their Excel sheet in the data lake. Is that big data? Not even close. It&#8217;s very small. But for some people it&#8217;s a first step in a data driven [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,75],"tags":[181,179,178,77,76,180,182,183],"class_list":["post-725","post","type-post","status-publish","format-standard","hentry","category-howto","category-python","tag-excel","tag-header","tag-multiindex","tag-pandas","tag-python","tag-space-fueling-stations","tag-stack","tag-unstack"],"_links":{"self":[{"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/posts\/725","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/comments?post=725"}],"version-history":[{"count":16,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/posts\/725\/revisions"}],"predecessor-version":[{"id":746,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/posts\/725\/revisions\/746"}],"wp:attachment":[{"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/media?parent=725"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/categories?post=725"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/marcel-jan.eu\/datablog\/wp-json\/wp\/v2\/tags?post=725"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}