{"id":1085,"date":"2019-02-15T10:31:58","date_gmt":"2019-02-15T10:31:58","guid":{"rendered":"http:\/\/kusuaks7\/?p=690"},"modified":"2023-07-28T14:06:35","modified_gmt":"2023-07-28T14:06:35","slug":"codify-your-workflow","status":"publish","type":"post","link":"https:\/\/www.experfy.com\/blog\/bigdata-cloud\/codify-your-workflow\/","title":{"rendered":"Codify your workflow"},"content":{"rendered":"<p><strong><em>Ready to learn Data Science? <a href=\"https:\/\/www.experfy.com\/training\/courses\">Browse courses<\/a>\u00a0like\u00a0<a href=\"https:\/\/www.experfy.com\/training\/tracks\/data-science-training-certification\">Data Science Training and Certification<\/a> developed by industry thought leaders and Experfy in Harvard Innovation Lab.<\/em><\/strong><\/p>\n<section>\n<h3 id=\"c1fc\">Encoding my workflow saves time and effort over the long-run, but it also requires me to be explicit about what my workflow is in the first\u00a0place.<\/h3>\n<figure id=\"6ea4\"><canvas width=\"75\" height=\"62\"><\/canvas><img decoding=\"async\" style=\"width: 650px; height: 548px;\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1600\/0*q0tT7UVkpWA_YHKs.\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/1600\/0*q0tT7UVkpWA_YHKs.\" \/><\/figure>\n<p id=\"ed71\">I think I\u2019m like most data scientists in that there are certain things I tend to do repeatedly across projects: there are certain patterns I check for, or certain things I plot, or certain relationships I try to understand. Sometimes it\u2019s because I\u2019ve made a mistake often enough that I find it pays to automatically guard against it. Sometimes it\u2019s because I just find that, personally, certain ways of looking at a problem help me wrap my mind around it. In any case, large parts of my workflow for any given project very often look very similar to large parts of my workflows for previous projects. Not surprisingly, I\u2019ve found it useful to reduce the overhead of these repetitive activities.<\/p>\n<p id=\"9078\">While I\u2019ve understood the value of reducing overhead through automation ever since I learned how to code, I never got much advice on how to do it. It\u2019s just something I picked up. In this post, I try to summarize some of the things I\u2019ve learned, and I\u2019ll use some code from a recently-automated part of my workflow as an example.<\/p>\n<h2 id=\"f86f\">Logical beauty<\/h2>\n<p id=\"2d61\">Good design advice has been around at least since Vitruvius wrote\u00a0<a href=\"https:\/\/books.google.com\/books?id=wYhAAAAAYAAJ&amp;printsec=frontcover\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/books.google.com\/books?id=wYhAAAAAYAAJ&amp;printsec=frontcover\" data->Ten Books on Architecture<\/a>\u00a0(still a worthwhile read), where he said that good design achieves firmness, usefulness, and delight. It\u2019s relatively easy to translate the first two of those ideals to workflow automation design: whatever we build, it should consistently do the job we want it to do at the scale, speed, and quality that we need. But I think it\u2019s the last ideal \u2014 delight \u2014 that most often makes the difference between a workable design and a good design.\u00a0<a href=\"https:\/\/books.google.com\/books?id=0qG4TQi-e-4C&amp;printsec=frontcover\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/books.google.com\/books?id=0qG4TQi-e-4C&amp;printsec=frontcover\" data->Fred Brooks argues<\/a>\u00a0that delight, in the context of technical design, should be thought of as \u201clogical beauty\u201d, and argues that\u00a0<strong><em>a logically beautiful product is one that minimizes mental effort<\/em><\/strong>.<\/p>\n<p id=\"8e7f\">I think minimization of mental effort is an interesting goal for a data science workflow, since many recent embarrassments in the data industry\u00a0<a href=\"https:\/\/towardsdatascience.com\/data-is-a-stakeholder-31bfdb650af0\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/towardsdatascience.com\/data-is-a-stakeholder-31bfdb650af0\" data->seem to have arisen<\/a>\u00a0from practitioners not expending enough mental effort thinking about the potential consequences of their design decisions.\u00a0<strong><em>So we need to carefully select which areas of mental effort to minimize<\/em><\/strong>. That means the first step of a good design is, no matter how trite it sounds, to decide what we want to accomplish.<\/p>\n<p id=\"1b68\">Think about driving a car. I drive a car to go from where I\u2019m going as efficiently as possible. A well-designed car should allow me to reserve as much of my attention as possible for those things most directly related to accomplishing my purpose. That means minimizing the mental effort on everything else. In other words, while I\u2019m driving, I should be able to focus on making my turn at the right place, on avoiding things like pedestrians or lampposts, and things like that. I should not be focused on making sure the engine runs, or on shifting gears, or on how the brakes work. If I used the car for another purpose I might focus on those things, but they are not\u00a0<em>proper<\/em>\u00a0to the accomplishment of the particular purpose I have chosen. A good design shouldn\u2019t distract me or prevent me from paying attention to the things that could prevent from from accomplishing my purpose.<\/p>\n<p id=\"ccdc\"><em>Propriety<\/em>\u00a0is a somewhat old-fashioned way of talking about relevance. Brooks defines propriety in design as \u201cdo not introduce what is immaterial.\u201d I\u2019ve found it useful to think about propriety in terms of the tradeoffs I used in the analogy. If doing something ensures that I am not prevented from accomplishing my purpose, it is proper to that purpose. If doing something just facilitates the the accomplishment of the purpose, it\u2019s not proper to that purpose. Failing to avoiding pedestrians or lampposts will necessarily prevent me from getting where I want to go. That is true if I travel by automobile, foot, skateboard, bike, airplane, or train. Failing to have a combustion engine won\u2019t necessarily prevent me from getting where I want to go; therefore, it\u2019s not proper to the design, no matter how convenient it is a matter of implementation. So after we\u2019ve defined our purpose, define what\u2019s proper to that purpose: as a rule of thumb, if it can move us forward, it may or may not be proper; if it can keep us from moving forward, it\u2019s proper.\u00a0<strong><em>Good design minimizes the mental effort expended on tasks that are not proper to our intended purpose<\/em><\/strong><em>.<\/em><\/p>\n<p id=\"957d\"><em>Orthogonality<\/em>\u00a0is another component of logical beauty. Brooks summarizes this as \u201cdo not link what is independent\u201d. In other words: my ability to avoid pedestrians and lamp posts partially depends upon being able to see those things, and partially upon being able to maneuver the car to avoid them. A windshield\u2019s functionality and a steering wheel\u2019s functionality should be independent of one another. A design that failed to maintain that independence would be a poor design. So after we define the proper components of our workflow, we minimize dependencies.\u00a0<strong><em>Good design keeps proper components separate from components that aren\u2019t proper so we don\u2019t accidentally minimize the mental effort we expend on the the things that actually need our full attention. Likewise, good design keeps non-proper components from each other so we don\u2019t accidentally minimize mental effort in ways we didn\u2019t anticipate.<\/em><\/strong><\/p>\n<p id=\"9659\"><em>Generality<\/em>\u00a0is the last principle I\u2019ll talk about before moving on to the example. Brooks summarizes it as \u201cdo not restrict what is inherent.\u201d In other words, a tire (or engine or wiring, etc.) that fits one car shouldn\u2019t be designed to fit\u00a0<em>only<\/em>that car without a very good reason.\u00a0<strong><em>Good design demands a reason before restricting the use cases to which a piece of functionality can apply.\u00a0<\/em><\/strong>This makes it easier to accommodate unanticipated needs without requiring us to rebuild the whole product.<\/p>\n<p id=\"4d06\">So, to summarize:<\/p>\n<ol>\n<li id=\"71b4\">Decide what purpose we want to accomplish.<\/li>\n<li id=\"2c47\">Identify which tasks are proper to the accomplishment of that purpose.<\/li>\n<li id=\"3e3c\">Minimize dependencies by separating proper tasks from non-proper tasks, and non-proper tasks from one another.<\/li>\n<li id=\"73b2\">Plan each task to be as general-purpose as possible while still maintaining propriety or orthogonality.<\/li>\n<\/ol>\n<p id=\"6ce5\">Designs that follow the above principles tend to be logically beautiful, in that they minimize our mental effort in all the right places.<\/p>\n<h2 id=\"6e2d\">A minimalist example of workflow\u00a0design<\/h2>\n<p id=\"7928\">I work a lot with geolocation data, and I often have the need to plot that data. Sometimes I need to plot shapes, and other times I need points. Sometimes I need to see those objects against the background of a satellite image to get some rough context, sometimes I need the more detailed context of roads or labels. I normally have to use some form of this workflow several times a week.\u00a0<a href=\"https:\/\/towardsdatascience.com\/data-is-a-stakeholder-31bfdb650af0\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/towardsdatascience.com\/data-is-a-stakeholder-31bfdb650af0\" data->Sometimes I have to go through it hundreds of times a day<\/a>. At any rate, I have to do it often enough that it\u2019s a good candidate for minimizing mental effort. So I only have one purpose: graphically represent longitude and latitude pairs on a picture that represents the real-world context of those coordinates. That purpose defines propriety: anything that doesn\u2019t actually put shapes on the picture is not proper to my purpose, and therefore mental effort expended on that thing should be minimized. In the case of the current example, defining purpose and propriety were relatively easy. That\u2019s not always the case.<\/p>\n<p id=\"33eb\">Now I need to worry about orthogonality. Imagine speaking to someone with absolutely no technical expertise. I usually picture either one of my earliest managers described everything involving code as \u201cnew-fangled\u201d, or my mother who still refuses to get an email address, or a college freshman majoring in the humanities. You get the picture. My task creates a set of questions that my imaginary novice would see as being both necessary and sufficient to create my desired outcome. If my imaginary novice can say \u201cbut why do you need the answer to X?\u201d, that means I\u2019ve included an unnecessary question. If my imaginary novice can say \u201c but I don\u2019t understand how the answers to X, Y, and Z will lead to the outcome\u201d, that means I\u2019m either missing a question or haven\u2019t asked the right question. The reason I like to use an imaginary novice as a sounding board is because I tend to get too technical too fast when I try to design everything. Because I know how to implement things, my brain jumps to implementation questions sooner than it should. If you have a real flesh-and-blood novice who is willing and able to be your sounding board, use that person instead of an imaginary one.<\/p>\n<p id=\"c3b6\">If my goal is to get shapes on a picture, then I have only three questions:<\/p>\n<ul>\n<li id=\"2bf5\">Where do I get the shapes?<\/li>\n<li id=\"25f9\">Where do I get the picture?<\/li>\n<li id=\"3c11\">How do I get the shapes on the picture?<\/li>\n<\/ul>\n<p id=\"2bd4\">Pretty basic questions, right? But these basic questions force me to think in terms of foundational building blocks, which makes it easier to preserve orthogonality. If whatever I eventually build operates in a way that I can\u2019t change how I get shapes without changing how I put those shapes on a picture, then I\u2019ve probably built the thing wrong. The different pieces of functionality should be as independent as possible.<\/p>\n<p id=\"0cdf\">Now that I have orthogonal components, I want to make them as general as possible. For that, I need use cases. I\u2019m already biased with preferred ways of doing of the three things I need to do, but I\u2019ll be better off in the long run if I can think of as many ways \u2014 preferred or not \u2014 as I can. I might not implement support for all the use cases I list: that will depend on the constraints of the project (mostly how much time, how many resources, and how many competing priorities I have). It\u2019s still best to know as many use cases as possible up front \u2014 often, in the course of implementing support for critical use cases, it\u2019s pretty trivial to support additional, as-yet-not-needed use cases simply as a matter of course. This can reduce frustration and wasted time down the road. Here are a bunch of use-cases for the current example:<\/p>\n<p id=\"00b4\"><strong>Where do I get the shapes?<\/strong><\/p>\n<ul>\n<li id=\"64ac\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Geographic_coordinate_system\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/en.wikipedia.org\/wiki\/Geographic_coordinate_system\" data->Longitude\/latitude pairs<\/a><\/li>\n<li id=\"2776\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Well-known_text\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/en.wikipedia.org\/wiki\/Well-known_text\" data->Well-known text<\/a><\/li>\n<li id=\"e19d\"><a href=\"https:\/\/shapely.readthedocs.io\/en\/latest\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/shapely.readthedocs.io\/en\/latest\/\" data->Shapely objects<\/a><\/li>\n<li id=\"d078\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Geohash\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/en.wikipedia.org\/wiki\/Geohash\" data->Geohashes<\/a><\/li>\n<\/ul>\n<p id=\"4e48\"><strong>Where do I get the picture?<\/strong><\/p>\n<ul>\n<li id=\"cc83\"><a href=\"https:\/\/developers.google.com\/maps\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/developers.google.com\/maps\/\" data->Google maps<\/a><\/li>\n<li id=\"60b9\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Web_Map_Tile_Service\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/en.wikipedia.org\/wiki\/Web_Map_Tile_Service\" data->Web map tile service<\/a><\/li>\n<\/ul>\n<p id=\"bb98\"><strong>How do I get the shapes on the picture?<\/strong><\/p>\n<ul>\n<li id=\"8746\"><a href=\"https:\/\/bokeh.pydata.org\/en\/latest\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/bokeh.pydata.org\/en\/latest\/\" data->Bokeh<\/a><\/li>\n<li id=\"6e77\">GeoViews\/<a href=\"http:\/\/holoviews.org\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"http:\/\/holoviews.org\/\" data->HoloViews<\/a><\/li>\n<li id=\"c39c\"><a href=\"https:\/\/matplotlib.org\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"https:\/\/matplotlib.org\/\" data->Matplotlib<\/a><\/li>\n<li id=\"eb45\"><a href=\"http:\/\/leafletjs.com\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-href=\"http:\/\/leafletjs.com\/\" data->Leaflet.js<\/a>\u00a0(via\u00a0Folium)<\/li>\n<\/ul>\n<p id=\"abd2\">The list isn\u2019t exhaustive, of course. If I had more time to think about it, I could build out a more complete list, and would probably be better off for it. But as I have limited time and other things to do, I\u2019m content that I have at least two answers for every question. All I\u2019m really trying to do is avoid a myopic focus on only my immediate needs.<\/p>\n<h2 id=\"f8cc\">From design to implementation<\/h2>\n<p id=\"d2d8\">You can jump to the bottom of this section to see the full code I implemented, but it might be helpful to read through this discussion first to understand what I did.<\/p>\n<p id=\"97fc\">I chose to rely entirely on the Bokeh library to plot the shapes on the pictures because I already know and like that library \u2014 it was easy to get something up and running quickly. Likewise, I chose to rely entirely on Google Maps to get the picture because it provides a single source for satellite images, maps, and annotated versions of both of those two things, and because Bokeh already offers a convenient way to call that Google Maps API.<\/p>\n<p id=\"9674\">I did very little to minimize my mental effort for those parts of my workflow, because crafting the look and feel of my visualization \u2014 choosing the background image, deciding what shapes to plot, and styling and annotating those shapes \u2014 is all proper to my purpose, so I want to maintain as much granular control over those things as possible.<\/p>\n<p id=\"6680\">The Python class I implemented has a `prepare_plot` method, which for the most part just takes optional keyword arguments that get passed to a Bokeh `GMapPlot` object that the method instantiates:<\/p>\n<p>&nbsp;<\/p>\n<p id=\"7c95\">The class also has an `add_layer` method for placing shapes on the picture:<\/p>\n<p>&nbsp;<\/p>\n<p id=\"19b4\">This method call has only two required arguments. One is a label that tells the method what data source the shape should reference (again, more on that later). The other is a Bokeh model \u2014 the two models I use most often are `Patches`, for plotting polygons, and `Circle`, for plotting coordinate pairs. There is an optional `tooltips` argument that takes either a string or a list of tuples to\u00a0create tooltips\u00a0that should appear when a mouse hovers over a shape. All other keyword argument are passed to the Bokeh model, which gives me granular control over how the shape looks. Most Bokeh models allow both the border and the area of a shape to be styled differently. For example, if I wanted a red dot with a black border, I would set `fill_color` to red and `line_color` to black. Likewise, if I wanted the fill to be 50% transparent but the border to be 0% transparent, I would set `fill_alpha` to 0.5 and `line_alpha` to 1.0. Often, I want fill and line to be styled the same way, so the `add_layer` method accepts additional keywords. If I set `alpha` to 0.5, then both `fill_alpha` and `line_alpha` will be set to 0.5. It\u2019s a small reduction in mental effort, but it\u2019s enough of a reduction to be convenient.<\/p>\n<p id=\"8a54\">The majority of the mental-effort-reduction work I did, however, had to do with preparing the data for plotting. All four of the use cases I mentioned for getting shapes \u2014 longitude\/latitude pairs, well-known text, shapely objects, and geohashes \u2014 are use case that I regularly encounter in my daily work. I found that a lot of my time investigating data wasn\u2019t spent on actually trying to understand the plot, but rather on getting the data into the format I needed. I especially incurred a lot of overhead when I had to switch data source types part way through an analysis (for example, I may have set up a workflow to plot geo-coordinate pairs, and then suddenly found I needed to plot geohashes; that required me to restructure part of my workflow).<\/p>\n<p id=\"0605\">The `add source` method requires data and a label:<\/p>\n<p>&nbsp;<\/p>\n<p id=\"b76b\">The data can be a list containing any one of the four types of input values I\u2019ve mentioned, or it can be a dataframe where one column is comprised of those input values (and that column must be explicitly specified). This method performs four basic functions:<\/p>\n<ul>\n<li id=\"77bb\">It calls the `_process_input_value`, which automatically validates the input type (geohash vs. coordinate pair, etc.) and formats it into a list of x coordinates and a list of y coordinates. In the case of well-known text or a shapely object representing a MultiPolygon or some other collection of multiple geometries, it will format a list of lists. The `_process_input_value` method serves as a switchboard, completely eliminating the overhead of appropriately formatting input values, assuming the values are one of the supported types.<\/li>\n<li id=\"60d8\">It appends metadata to the input values. In the case of a DataFrame input, each column of the DataFrame that does not contain the input values will be used as metadata. In the case of a list input, any keyword arguments will be used as metadata (assuming the argument is a list of the same length as the input value list.<\/li>\n<li id=\"7703\">It expands the source DataFrame to accomodate multi-shapes. If an input value is something like a MultiPolygon, it will split that out into individual polygons, and append the MultiPolygon\u2019s metadata to each individual polygon. This is similar to the concept of exploding an array in SQL.<\/li>\n<li id=\"31a5\">After all of the above processing, it calls `_set_coordinate_bounds`, which updates the plot bounds (minimum and maximum x and y values). They bounds are referenced when determining the Google Maps zoom level.<\/li>\n<\/ul>\n<p id=\"901c\">The class does a bunch of other things, but for the most part, the structure of the class itself is nothing more than an instantiation of the three key parts of my workflow: get shapes, get a picture, put the shapes on the picture. Because I codified that workflow using a few sound design principles (purpose \u2192 propriety \u2192 orthogonality \u2192 generality), it\u2019s relatively easy to extend the class to accommodate new needs. For example, I\u2019d like to expand the class to use WMTS tiles instead of Google Maps. Because of the way the class is designed, I can do that more or less with changes only to the `prepare_plot` method, and nothing else. If I wanted to render the plot using Leaflet or Matplotlib or something else, that would require some more extensive renovation, but still the effects wouldn\u2019t ripple through the whole codebase.<\/p>\n<p id=\"7603\">The biggest benefit, however, is less a matter of how I wrote the code and more the fact that I wrote any code at all. Yes, encoding my workflow saves time and effort over the long-run, but\u00a0<strong><em>it also requires me to be explicit about what my workflow is in the first place<\/em><\/strong>. In writing this code, I identified several things about my workflow that really made me unhappy. For example, I hated how much time it took to manage the data when I wanted to plot polygons instead of points (or points instead of polygons), so I changed `add_source` method to always assume polygons and the `add_layer` method to automatically calculate a centroid and update the data source whenever a Bokeh model is used that assumes point data but references a data sources that contains polygon data. I had baked assumptions about the polygon vs. point structure of my data into my workflow without realizing it. By codifying my workflow, I recognized that assumption, decided I didn\u2019t like it, and removed it.<\/p>\n<p id=\"da5a\">Codifying my workflow makes my workflow better, and making my workflow better makes me a better coder. And in explicitly thinking about the design of both the code and the workflow, I\u2019ve made it easier to repurpose old work for new problems, and to share my work with colleagues.<\/p>\n<h2 id=\"d0fe\">The code<\/h2>\n<p id=\"5c9e\">I suppose it should go without saying that this code isn\u2019t meant to be a perfect representation of the design principles I outlined earlier. Design is a direction, not a destination. I\u2019m painfully aware of how much the code could stand to be improved!<\/p>\n<\/section>\n<footer>\u00a0<\/footer>\n","protected":false},"excerpt":{"rendered":"<p>Good design achieves firmness, usefulness, and delight. It&rsquo;s relatively easy to translate the first two of those ideas to workflow automation design: whatever we build, it should consistently do the job we want it to do at the scale, speed, and quality that we need. The first step of a good design is to decide what we want to accomplish. Encoding workflow saves time and effort over the long-run, but it also requires being explicit about what workflow is in the first&nbsp;place.<\/p>\n","protected":false},"author":238,"featured_media":3969,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"content-type":"","footnotes":""},"categories":[187],"tags":[94],"ppma_author":[1869],"class_list":["post-1085","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-bigdata-cloud","tag-data-science"],"authors":[{"term_id":1869,"user_id":238,"is_guest":0,"slug":"schaun-wheeler","display_name":"Schaun Wheeler","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","user_url":"","last_name":"Wheeler","first_name":"Schaun","job_title":"","description":"Schaun Wheeler is a senior data scientist at Valassis Digital, an advertising and marketing intelligence company. After building data science capabilities for the U.S Department of the Army and working as a data scientist in an educational travel firm and then an asset management startup, he founded and directed the data science team for Success Academy Charter schools in New York City. His current work focuses on location semantics and his broader interests include data science ethics, analytic design, and the creation of humane HR practices in the tech industry. He is a formally trained anthropologist."}],"_links":{"self":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1085","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/users\/238"}],"replies":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/comments?post=1085"}],"version-history":[{"count":4,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1085\/revisions"}],"predecessor-version":[{"id":29786,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1085\/revisions\/29786"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media\/3969"}],"wp:attachment":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media?parent=1085"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/categories?post=1085"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/tags?post=1085"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=1085"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}