<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Datatribute]]></title><description><![CDATA[Datatribute]]></description><link>https://datatribute.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1647273474155/vfn1WHDT-.png</url><title>Datatribute</title><link>https://datatribute.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 03:49:54 GMT</lastBuildDate><atom:link href="https://datatribute.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Our DMV Farewell Tour: 70+ Things to Do Around Washington, DC]]></title><description><![CDATA[After nearly six years in Arlington, my significant other and I are moving several states away. Before we packed the last box, we started asking the question every departing local eventually asks:

Di]]></description><link>https://datatribute.com/our-dmv-farewell-tour-70-things-to-do-around-washington-dc</link><guid isPermaLink="true">https://datatribute.com/our-dmv-farewell-tour-70-things-to-do-around-washington-dc</guid><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Wed, 29 Jul 2026 19:25:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/61dee6614e70945324c59e25/d3b744e7-e0af-44f1-a68d-a4d3aa273371.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After nearly six years in Arlington, my significant other and I are moving several states away. Before we packed the last box, we started asking the question every departing local eventually asks:</p>
<blockquote>
<p>Did we make the most of living here?</p>
</blockquote>
<p>So we wrote down everything we had experienced across Northern Virginia, Washington, DC, and Maryland. What began as a personal inventory became a surprisingly useful DMV bucket list—and when I <a href="https://www.reddit.com/r/nova/comments/1ised4g/leaving_last_things_to_do/">shared it with r/nova</a>, the community added even more ideas.</p>
<p>This is not a rigid itinerary or a ranking. It is a farewell tour built from six years of museums, trails, monuments, games, day trips, bakeries, and meals. Whether you are new to the area, hosting visitors, or preparing to leave like us, I hope it helps you notice just how much is within reach.</p>
<blockquote>
<p><strong>The short version:</strong> Walk the monuments after dark, watch a plane roar over Gravelly Point, spend a day in the free museums, get out on the Potomac, hike west of the Beltway, eat something you cannot easily find elsewhere, and save room for ice cream.</p>
</blockquote>
<h2>Virginia: views, trails, history, and easy escapes</h2>
<p>Northern Virginia is at its best when you move between the Potomac waterfront, small historic towns, and the foothills to the west.</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/GravellyPointPlaneWatching1.jpg/1280px-GravellyPointPlaneWatching1.jpg" alt="A plane taking off above Gravelly Point near Reagan National Airport" style="display:block;margin:0 auto" />

<p><em>A plane departs Reagan National above Gravelly Point. Photo by Carl Lindberg,</em> <a href="https://commons.wikimedia.org/wiki/File:GravellyPointPlaneWatching1.jpg"><em>Wikimedia Commons</em></a><em>,</em> <a href="https://creativecommons.org/licenses/by/2.5/"><em>CC BY 2.5</em></a><em>.</em></p>
<h3>Along the Potomac</h3>
<ul>
<li><p><strong>Mount Vernon and the Mount Vernon Trail:</strong> George Washington's estate makes the history tangible; the paved trail turns the riverfront into a long, scenic ride or run back toward Arlington.</p>
</li>
<li><p><strong>Gravelly Point:</strong> Spread out a blanket and wait for arriving or departing planes to pass startlingly close overhead. It is simple, free, and one of the area's most memorable experiences.</p>
</li>
<li><p><strong>Old Town Alexandria:</strong> Wander King Street without a plan, explore the side streets, and finish at the waterfront. It works equally well as an afternoon stroll or a full date night.</p>
</li>
<li><p><strong>Great Falls:</strong> The Virginia overlooks deliver the drama quickly, while the surrounding trails make it easy to extend the visit. Seeing the Potomac squeeze through the rocky gorge never gets old.</p>
</li>
<li><p><strong>Pohick Bay and Fountainhead Regional Parks:</strong> Pohick is an easy place to get onto the water; Fountainhead offers wooded trails, paddling, and a quick sense of escape without driving all the way to the mountains.</p>
</li>
</ul>
<h3>Arlington essentials</h3>
<ul>
<li><p><strong>Arlington National Cemetery:</strong> Give it more time than a rushed stop at the Tomb of the Unknown Soldier. The scale, hills, and quiet make this one of the most moving places in the region.</p>
</li>
<li><p><strong>US Marine Corps War Memorial and the Netherlands Carillon:</strong> The famous flag-raising sculpture, open lawn, carillon, and skyline view make this an excellent sunset combination.</p>
</li>
<li><p><strong>The old Rosslyn observation deck:</strong> The View of DC once offered a remarkable panorama from Central Place Tower. It has since closed to the public, so this one survives on our list with a respectful “RIP.”</p>
</li>
</ul>
<h3>History and aviation</h3>
<ul>
<li><p><strong>Manassas National Battlefield Park:</strong> Walk the fields rather than only stopping at the visitor center. The open landscape helps the history register in a way a display case cannot.</p>
</li>
<li><p><strong>Steven F. Udvar-Hazy Center:</strong> The National Air and Space Museum's enormous companion facility near Dulles is worth the trip for the Space Shuttle <em>Discovery</em> alone—then you notice the Concorde, the SR-71, and hangars full of aircraft.</p>
</li>
<li><p><strong>Harpers Ferry:</strong> Technically in West Virginia, but too close and too good to omit. Historic streets, river views, and trail access make it one of the best all-purpose day trips from Northern Virginia.</p>
</li>
</ul>
<h3>Head west</h3>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Shenandoah_National_Park_SHEN9178.jpg/1280px-Shenandoah_National_Park_SHEN9178.jpg" alt="Blue Ridge mountain views in Shenandoah National Park" style="display:block;margin:0 auto" />

<p><em>Shenandoah National Park. Photo by the National Park Service,</em> <a href="https://commons.wikimedia.org/wiki/File:Shenandoah_National_Park_SHEN9178.jpg"><em>Wikimedia Commons</em></a><em>, public domain.</em></p>
<ul>
<li><p><strong>Sky Meadows State Park:</strong> A rewarding option when you want mountain scenery and Appalachian Trail access without committing to the longer drive into Shenandoah.</p>
</li>
<li><p><strong>Shenandoah National Park:</strong> Skyline Drive is the easy introduction, but the real reward is getting onto a trail. We used the park as a gateway to many hikes westward, in every season.</p>
</li>
<li><p><strong>Leesburg:</strong> A walkable historic center, nearby wineries and breweries, and easy access to western Loudoun make it a flexible day trip.</p>
</li>
<li><p><strong>Meadowlark Botanical Gardens:</strong> A slower outing, in the best way. Walk the gardens in bloom or return in winter for the seasonal lights.</p>
</li>
<li><p><strong>Charlottesville:</strong> Farther afield, but manageable for a long day or weekend. The Downtown Mall, nearby vineyards, Blue Ridge setting, and layers of history justify the drive.</p>
</li>
</ul>
<h2>Washington, DC: use the access while you have it</h2>
<p>DC can be easy to postpone precisely because it is nearby. We learned to treat the city's museums, performances, sports, and public spaces as the extraordinary local amenities they are.</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Tidal_Basin_cherry_blossoms.jpg/1280px-Tidal_Basin_cherry_blossoms.jpg" alt="Washington Monument and cherry blossoms reflected in the Tidal Basin" style="display:block;margin:0 auto" />

<p><em>The Tidal Basin during cherry blossom season. Photo by AgnosticPreachersKid,</em> <a href="https://commons.wikimedia.org/wiki/File:Tidal_Basin_cherry_blossoms.jpg"><em>Wikimedia Commons</em></a><em>,</em> <a href="https://creativecommons.org/licenses/by-sa/4.0/"><em>CC BY-SA 4.0</em></a><em>.</em></p>
<h3>Museums worth returning to</h3>
<p>The Smithsonian museums are free, which means you do not have to “finish” one in a single exhausting visit. We made time for the <strong>National Museum of Natural History</strong>, <strong>National Museum of American History</strong>, <strong>National Air and Space Museum</strong>, and <strong>National Postal Museum</strong>. Each rewards repeat visits and a willingness to follow whatever catches your attention.</p>
<p>Outside that core, we explored the <strong>International Spy Museum</strong>, <strong>Smithsonian's National Zoo</strong>, <strong>United States Botanic Garden</strong>, and <strong>U.S. National Arboretum</strong>. The Arboretum is especially striking when the azaleas bloom, while the Botanic Garden is an excellent winter reset.</p>
<p>The <strong>Old Post Office Tower</strong>—not to be confused with the Postal Museum—is a wonderfully odd free viewpoint hidden inside the Waldorf Astoria's historic building. It remains one of the city's less obvious ways to look over downtown.</p>
<h3>Monuments, landmarks, and seats of government</h3>
<ul>
<li><p><strong>The National Mall memorial circuit:</strong> The Lincoln, Jefferson, Martin Luther King Jr., and Korean War Veterans memorials all deserve time, not just a drive-by. Add the Vietnam Veterans and World War II memorials if they are not already on your route.</p>
</li>
<li><p><strong>The Washington Monument:</strong> Seeing it from below is one thing; reserving a trip to the top changes your sense of the city's layout.</p>
</li>
<li><p><strong>The U.S. Capitol and White House:</strong> Public tours take advance planning, but living nearby makes that planning far easier than it will ever be after moving away.</p>
</li>
<li><p><strong>Washington National Cathedral:</strong> Even in a city of imposing architecture, the cathedral feels distinct. Explore the interior, grounds, and hilltop setting rather than treating it as a photo stop.</p>
</li>
<li><p><strong>Cherry blossoms:</strong> Yes, peak bloom is crowded. It is also beautiful. Go very early, stay late, or walk through Hains Point for a little more breathing room.</p>
</li>
</ul>
<p><strong>Our favorite twist:</strong> revisit the monuments at night. The crowds thin, the stone glows, and familiar landmarks feel unexpectedly dramatic. A sunrise from the Lincoln Memorial—one of the best suggestions from the Reddit thread—is an equally strong finale.</p>
<h3>Markets, gardens, and places to wander</h3>
<ul>
<li><p><strong>Union Market and Eastern Market:</strong> These are different experiences, and both are worth doing. Union Market is a modern food-hall stop; Eastern Market is best paired with a longer Capitol Hill walk.</p>
</li>
<li><p><strong>Dumbarton Oaks:</strong> The museum and formal gardens feel far removed from the busier parts of Georgetown.</p>
</li>
<li><p><strong>Hillwood Estate, Museum &amp; Gardens:</strong> The former home of Marjorie Merriweather Post combines decorative art, Russian imperial objects, and beautifully designed grounds.</p>
</li>
<li><p><strong>Theodore Roosevelt Island:</strong> A shaded, low-key walk in the middle of the Potomac, reached from the Virginia side.</p>
</li>
<li><p><strong>Hains Point:</strong> Walk, run, bike, picnic, or use it as a quieter extension of a Tidal Basin outing.</p>
</li>
<li><p><strong>The Wharf:</strong> Come for the waterfront, a meal, a show, or a boat connection. It is also an ideal starting point for seeing the monuments from the river.</p>
</li>
</ul>
<h3>Shows, games, and nights out</h3>
<ul>
<li><p><strong>The Kennedy Center:</strong> A performance here feels like an occasion. Even without a ticket, the terraces and river views make the building worth exploring.</p>
</li>
<li><p><strong>Capitals hockey at Capital One Arena:</strong> Few things turn downtown into a shared living room as quickly as a hockey night.</p>
</li>
<li><p><strong>A Nationals game at Nationals Park:</strong> Baseball is only part of the experience; arriving along the river and lingering around Navy Yard complete the outing.</p>
</li>
<li><p><strong>DC United at Audi Field:</strong> A more compact stadium and an energetic crowd give soccer matches their own character.</p>
</li>
<li><p><strong>Echostage:</strong> Big electronic shows belong here, but the city also shines at smaller rooms such as the 9:30 Club, The Atlantis, Black Cat, Union Stage, and The Anthem.</p>
</li>
<li><p><strong>Passport DC and the spring embassy open houses:</strong> For a few weekends each year, embassies invite the public inside for food, music, art, and cultural programs. It is one of those events that could happen only in Washington.</p>
</li>
</ul>
<h3>Get onto the water</h3>
<p>Kayaking on the Potomac changes the scale of the city. Paddle from one of the local boathouses, take a sunset cruise, or ride a water taxi between waterfront neighborhoods. We also made time for <strong>National Harbor</strong>, including the Gaylord resort and MGM casino—a polished, very different contrast to the historic core of DC.</p>
<h2>Maryland: maritime days and a little more history</h2>
<p>Maryland gave us some of the easiest escapes from the DC routine: waterfront streets, crabs on paper-covered tables, breweries, and forts looking back across the Potomac.</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Annapolis_harbor.jpg/1280px-Annapolis_harbor.jpg" alt="Boats in the harbor in central Annapolis, Maryland" style="display:block;margin:0 auto" />

<p><em>Annapolis Harbor. Photo by Bernini123,</em> <a href="https://commons.wikimedia.org/wiki/File:Annapolis_harbor.jpg"><em>Wikimedia Commons</em></a><em>,</em> <a href="https://creativecommons.org/publicdomain/zero/1.0/"><em>CC0</em></a><em>.</em></p>
<ul>
<li><p><strong>Annapolis:</strong> Walk the harbor and the narrow historic streets, watch boats move through Ego Alley, and let the day unfold without a packed schedule.</p>
</li>
<li><p><strong>A proper Maryland crab feast:</strong> The point is as much the ritual as the food: mallets, Old Bay, brown paper, and no reason to rush.</p>
</li>
<li><p><strong>Baltimore's Inner Harbor:</strong> Use the harbor as a starting point, not the entire itinerary. It connects easily to historic ships, museums, Federal Hill, and nearby neighborhoods.</p>
</li>
<li><p><strong>Guinness Open Gate Brewery:</strong> The Maryland brewery pairs a familiar name with an unexpectedly expansive taproom and grounds.</p>
</li>
<li><p><strong>Old Ellicott City:</strong> Hillside streets, old stone buildings, shops, and the river make it a charming half-day wander.</p>
</li>
<li><p><strong>Fort Washington Park:</strong> The fort's position above the Potomac explains its purpose immediately and provides a quieter historic outing than many downtown sites.</p>
</li>
</ul>
<h2>Our DMV food-and-drink memory map</h2>
<p>Food may be what we miss most. The region's strength is not one signature dish; it is the ability to move among Ethiopian, Vietnamese, Filipino, Italian, Palestinian, Korean, and American cooking in the span of a few Metro stops or a short drive.</p>
<p>Here are the places that became part of our six years:</p>
<ul>
<li><p><strong>Pizza and Italian:</strong> Stracci Pizza, Sfoglina, Filomena, and The Italian Store.</p>
</li>
<li><p><strong>Casual Northern Virginia favorites:</strong> Pho 75, Hot Lola's, Galaxy Hut, and Silver Diner.</p>
</li>
<li><p><strong>DC and neighborhood staples:</strong> Purple Patch, Busboys and Poets, Falafel Inc, Yellow, Call Your Mother, Habesha Market, and Founding Farmers.</p>
</li>
<li><p><strong>Bakeries and sweets:</strong> Levain Bakery, Georgetown Cupcake, Blue Bottle, and Jeni's Ice Cream.</p>
</li>
<li><p><strong>Beer:</strong> Port City Brewing Company and Aslin Beer Company in Northern Virginia and DC.</p>
</li>
</ul>
<p>This is a memory list, not an attempt to name every “best” restaurant. Businesses change, menus evolve, and locations close, so check current details before making a special trip.</p>
<h3>What the Reddit thread told us to eat next</h3>
<p>The strongest community theme was to lean into food that is unusually good—or unusually easy to find—in the DMV:</p>
<ul>
<li><p>Korean food and Korean barbecue in <strong>Annandale</strong>, plus Vietnamese food at <strong>Eden Center</strong>.</p>
</li>
<li><p>Peruvian chicken, Ethiopian food, and Bolivian <strong>salteñas</strong>.</p>
</li>
<li><p>A meal at one of <strong>José Andrés's</strong> DC restaurants.</p>
</li>
<li><p><strong>L'Auberge Chez François</strong> or <strong>2941</strong> for a farewell dinner in Northern Virginia.</p>
</li>
<li><p><strong>The Dabney</strong>, <strong>Albi</strong>, or <strong>St. Anselm</strong> for a celebratory DC meal.</p>
</li>
<li><p>A burger and Ozzie rolls at a <strong>Great American Restaurants</strong> location.</p>
</li>
<li><p><strong>Ben's Chili Bowl</strong> for the history and ritual of the original U Street location.</p>
</li>
</ul>
<p>The practical advice was even better: return to the restaurants <em>you</em> will miss. A final meal does not have to be the most acclaimed one. It has to taste like your time here.</p>
<h2>The best ideas we had missed</h2>
<p>The Reddit comments turned our retrospective into a new shortlist. If we had only a few open weekends left, these would rise to the top:</p>
<ol>
<li><p><strong>See Great Falls from Maryland and hike the Billy Goat Trail.</strong> The canal, boardwalks, and rocky trail provide a completely different experience from the Virginia overlooks. Check <a href="https://www.nps.gov/choh/planyourvisit/conditions.htm">current trail conditions</a> before going.</p>
</li>
<li><p><strong>Visit the National Portrait Gallery and Renwick Gallery.</strong> Both are manageable without devoting an entire day, and both expand the usual Smithsonian rotation.</p>
</li>
<li><p><strong>Walk Huntley Meadows Park.</strong> Its wetland boardwalk offers an easy wildlife escape surprisingly close to Alexandria.</p>
</li>
<li><p><strong>Spend an afternoon in Occoquan or Middleburg.</strong> Both reward an unhurried walk; choose Occoquan for the river and compact historic district, or Middleburg for horse-country scenery and nearby vineyards.</p>
</li>
<li><p><strong>Hike Old Rag or visit Bears Den Overlook.</strong> Old Rag is the bigger physical commitment and may require advance planning; Bears Den supplies a quicker Appalachian Trail payoff.</p>
</li>
<li><p><strong>Visit the National Museum of the Marine Corps and the National Museum of the United States Army.</strong> These free Virginia museums were two conspicuous gaps in our history list.</p>
</li>
<li><p><strong>See the National Archives and Library of Congress.</strong> When else can you casually visit the country's founding documents and one of its most spectacular interiors?</p>
</li>
<li><p><strong>Ride the C&amp;O Canal Towpath.</strong> One ambitious suggestion was to take the train to Harpers Ferry, stay overnight, and bike roughly 60 mostly traffic-free miles back to Georgetown.</p>
</li>
<li><p><strong>Go farther east to Assateague or Chincoteague.</strong> The wild horses, beaches, and island pace require more driving, but they feel worlds away from the Beltway.</p>
</li>
<li><p><strong>Do one final, ridiculous Beltway lap.</strong> This was offered partly as a joke. After six years in Northern Virginia traffic, perhaps it is the most authentic farewell of all.</p>
</li>
</ol>
<h2>What six years in the DMV taught us</h2>
<p>It is impossible to finish this region. There will always be another museum wing, trail, restaurant, performance, historic town, or overlook that you somehow missed.</p>
<p>That is not a failure of planning. It is the gift of living here.</p>
<p>Our list started as a way to check for omissions before moving. Instead, it became a record of how full these six years had been: planes over Gravelly Point, late innings by the Anacostia, quiet walks through gardens, mountain overlooks, crowded cherry blossoms, and far too many good meals to name.</p>
<p>If you live in the DMV, choose one thing from this list that you keep saying you will do “someday.” Put it on the calendar. Proximity is a privilege, but only if you use it.</p>
<p>And if you are leaving, do not spend your final weeks trying to complete everything. Revisit the places that already feel like yours.</p>
<hr />
<p><em>This guide grew from my original</em> <a href="https://www.reddit.com/r/nova/comments/1ised4g/leaving_last_things_to_do/"><em>r/nova farewell post</em></a> <em>and the generous recommendations that followed. Attraction hours, reservation systems, trail access, and restaurant locations change; check official sites before making a special trip.</em></p>
]]></content:encoded></item><item><title><![CDATA[Computer Science Masters - Course Review]]></title><description><![CDATA[As of 08-03-2026 I have taken 10/10 classes for the OMSCS program, and I am set to graduate!
KBAI - Knowledge Based Artificial Intelligence
Core Themes

BFS - Breadth First Search

DFS - Depth First S]]></description><link>https://datatribute.com/computer-science-masters-course-review</link><guid isPermaLink="true">https://datatribute.com/computer-science-masters-course-review</guid><category><![CDATA[omscs]]></category><category><![CDATA[Masters]]></category><category><![CDATA[Computer Science]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Wed, 26 Mar 2025 19:35:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JYqLCa-rv7o/upload/f74595b48b507749d174ea089c49cc55.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As of <em>08-03-2026</em> I have taken 10/10 classes for the <a href="https://omscs.gatech.edu/">OMSCS program</a>, and I am set to graduate!</p>
<h3>KBAI - Knowledge Based Artificial Intelligence</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>BFS - Breadth First Search</p>
</li>
<li><p>DFS - Depth First Search</p>
</li>
<li><p>A* Search</p>
</li>
<li><p>Heuristics</p>
</li>
</ul>
<p>The semester long, core project of the class is designing a software agent to solve the <a href="https://en.wikipedia.org/wiki/Raven%27s_Progressive_Matrices">Raven’s Progressive Matrices</a> problems. A series of visual tests that measures human’s intelligence. An example is below.</p>
<img src="https://www.123test.com/content/ravens-matrices-explanation-question.png" alt="" style="display:block;margin:0 auto" />

<div>
<div>💡</div>
<div><strong>Takeaway: </strong>When designing software, it’s extremely relevant to understand the layered cognitive reasoning and problem solving steps humans go through; translating those human techniques into code.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score: </strong>8/10. I enjoyed the cognitive science approach to software, albeit the lectures were not engaging and repetitive. The mini-projects in KBAI were well designed, engaging and a lot of fun!</div>
</div>

<p><code>Fall 2023</code></p>
<hr />
<h3>ML4T - Machine Learning for Trading</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>Software in the Stock Market</p>
<ul>
<li><p>Stock Market Financial Overview</p>
</li>
<li><p>Technical Indicators</p>
</li>
<li><p>High Frequency Trading (HFT)</p>
</li>
</ul>
</li>
<li><p>Linear Regression</p>
</li>
<li><p>Decision Trees - Random Forest Learner</p>
</li>
<li><p>Reinforcement Learning - Q-Learner/Dyna</p>
</li>
</ul>
<p>The final project in the class combines the prior machine learning techniques. The goal of the final project is to create a machine learning model where-in the rules are dynamically determined based on a trained supervised classification random forest model. The model was then deployed in a hypothetical stock simulation where decisions are made to buy and sell stock depending on the model’s attributes and the subsequent technical indicators.</p>
<p>The screenshot below depicts my trained model, the Strategy Learner, outperforming the portfolio of the Benchmark and Manual Strategy.</p>
<img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcrpZj7l8sbD3dX5d7gDSvcrMJWc5QASkBge2iJQJYvjWccjC8SP-zd0vdHVpvTJBjj9LD3Ltd-6R7n-wSuamWdf-AGrCMSWtoMfi0wmTilzoN1HLXZckXJjykIBhxsGsTeLDedEaLa2rbTV7PEdIy9zhAU?key=vbImr3s3HMgpnDoQSm1Xjw" alt="" />

<div>
<div>💡</div>
<div><strong>Takeaway: </strong>Machine Learning can be fairly digestable to understand and implement, it’s a lot of math implemented in such a way that can surface amazing insights and predictability. Machine Learning can be deployed in a lot of industries and the analytical, productivity and revenue gains can be monumental.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score:</strong> 10/10. I’m fascinated with parts of the finance industry. Dr. Balch is a great lecturer, the content and projects were very engaging.</div>
</div>

<p>The classic iris flower classification example is a great dataset to get your feet wet in Machine Learning!!</p>
<p><code>Spring 2024</code></p>
<hr />
<h3>SDP - Software Development Process</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>Mobile Application Development (JAVA) - Android Studio</p>
</li>
<li><p>Git/GitHub</p>
</li>
<li><p>Blackbox Testing</p>
</li>
<li><p>Whitebox Testing</p>
<ul>
<li>Path, Statement, Branch Coverage</li>
</ul>
</li>
<li><p>Unit Test Generation</p>
</li>
</ul>
<p>The larger group project in this class is developing an application in JAVA, using Android Studio. The loose structure of working on software in a typical SDLC process was something I’m used to in my full time work. However, it was a great learning experience in the context of JAVA and Android Studio.</p>
<p>The whitebox testing assignments were very engaging and reinforced learning about the different coverage techniques.</p>
<p>The screenshot below is the entry panel of the application my team and I developed.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743013732730/5afa8a60-c5ee-411f-a8d9-ee9b6a766d72.png" alt="" style="display:block;margin:0 auto" />

<div>
<div>💡</div>
<div><strong>Takeaway: </strong>Testing code is critical. Prior to this class I thought I was good at testing edge cases but I did so informally. Learning about whitebox testing helped formalize the quality assurance process that is often overlooked or underutilized in software development.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score: </strong>9/10 This was my first exposure to formal software testing, I found it fascinating to learn about the different coverage techniques for unit tests. Additionally the group project was a lot of fun. JAVA and mobile development is something I’ve experimented with but never worked a lot on. Getting back into JAVA helped my Python skills.</div>
</div>

<p><code>Summer 2024</code></p>
<hr />
<h3>AI4R - Artificial Intelligence for Robotics</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>Histogram Filter</p>
</li>
<li><p>Kalman Filter</p>
</li>
<li><p>Particle Filter</p>
</li>
<li><p>Kinematic Bicycle Model</p>
</li>
<li><p>PID Controller (Proportional, Integral, Derivative)</p>
</li>
<li><p>SLAM (Simultaneous Localization and Mapping)</p>
</li>
<li><p>Search (A-star, Dynamic Programming (value iteration, optimum policy))</p>
</li>
</ul>
<p>All of AI44’s projects are well thought-out and engaging. To complete the projects in full, you must understand the concepts at a fairly deep level. Each project comes with visual GUI interactions of the tasks, involving flying a drone to a certain height or localizing a moving asteroid.</p>
<p>The following gif is taken from the lectures, visualizing particle filters being constantly sampled and eventually through a particle filter, localizing on the target.</p>
<img src="https://assets.omscs.io/notes/particle-filters.gif" alt="" />

<p>The following visual is a demonstration of the Kalman filter. Predictions and measurements are fed into a filter, based on weights of each model, the prediction and measurements overtime converges into the estimated position of the vehicle.</p>
<img src="https://i.ytimg.com/vi/LioOvUZ1MiM/maxresdefault.jpg" alt="Kalman Filter - Part 1" />

<p>Lastly, extra credit for the class was implementing a Kalman Filter within moving physical hardware. The following video demonstrates my imperfect Kalman Filter robot turning and stopping autonomously based on ultra sonic measurements.</p>
<p><a class="embed-card" href="https://www.youtube.com/watch?v=K2woOHnF7-0">https://www.youtube.com/watch?v=K2woOHnF7-0</a></p>

<div>
<div>💡</div>
<div><strong>Takeaway:</strong> Localization is the core of robotics. Robots can’t perform tasks or actions if they don’t know where they are in the world. There’s a lot of concepts that go into mapping and localization for autonomous vehicles; these concepts are foundational to robotics, even in the age of AI.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score: </strong>8/10<strong> </strong>I learned the most array of concepts in this class. The projects were approachable and the concepts are not terribly mind-bending. Through the projects and the lectures, the instructors give just enough to initially cling to the concepts and then further research is required on the student to fully grasp the ideas.</div>
</div>

<p><code>Fall 2024</code></p>
<hr />
<h3>CN - Computer Networks</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>The Open Systems Interconnection (OSI) Model</p>
<ul>
<li>7 Layer Framework</li>
</ul>
</li>
<li><p>iBGP/eBGP</p>
</li>
<li><p>TCP/UDP</p>
</li>
<li><p>Congestion Control</p>
</li>
<li><p>PyBGPStream</p>
</li>
<li><p>BGP Hijacking/Measurement</p>
</li>
<li><p>Distance Vector Routing</p>
</li>
<li><p>SDN Firewall</p>
</li>
</ul>
<img src="https://www.practicalnetworking.net/wp-content/uploads/2016/01/packtrav-osi-layers-236x300.png" alt="OSI Model" />

<div>
<div>💡</div>
<div><strong>Takeaway: </strong>Computer Networks are extremely intricate. It’s fascinating the complexity that lives beneath mundane daily tasks for most of the world, checking your bank account, loading a YouTube video, refreshing a page. Given that, there is heavy abstraction and interoperability that has made networking much easier, such as SDN. However, the foundations of networking are extremely complex.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score: </strong>6.5/10 CN was a flurry of information. The lectures were tough to follow and grasp; I personally did not have much interest in the deeper concepts of networking. The projects however were fun and overall the course was fairly easy.</div>
</div>

<p><code>Spring 2025</code></p>
<hr />
<h3>VGD - Video Game Design &amp; Programming</h3>
<p><strong>Core Themes (Unity)</strong></p>
<ul>
<li><p>AI Tracking Agents</p>
</li>
<li><p>Animation Mecanim</p>
</li>
<li><p>Game Feel</p>
</li>
<li><p>Game Physics</p>
</li>
<li><p>Game Storytelling</p>
</li>
</ul>
<p>The largest aspect of VGD is a group project to design and build a 3D game. Our group created a maze based, reward finding, combat game called Maze Runner. Below is our game trailer.</p>
<p><a class="embed-card" href="https://www.youtube.com/watch?v=fKRjrjPJjEE&amp;feature=youtu.be">https://www.youtube.com/watch?v=fKRjrjPJjEE&amp;feature=youtu.be</a></p>

<div>
<div>💡</div>
<div><strong>Takeaway: </strong>Unity is incredibly powerful. I dare to say it’s ‘easy’ to make a 3D game in 2025. However, Unity provides all the tools and resources to be able to craft really modern games from your bedroom. There is an extremely steep learning curve and like anything else, there are multiple ways to accomplish a desired outcome.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score</strong>: 5/10 The course is not too challenging. I enjoyed poking around in Unity and creating a game from scratch but I am personally not interested in the intricacies of game development. I enjoyed brushing up on C#. The lectures are well done if you have an interest in this field.</div>
</div>

<p><code>Spring 2025</code></p>
<hr />
<h3>AIES - Artificial Intelligence Ethics &amp; Society</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>Fairness, Bias, Legality of Data Collection</p>
</li>
<li><p>Fairness in AL/ML</p>
</li>
<li><p>Bias Mitigation in AI</p>
<ul>
<li><p>Disparate Impact</p>
</li>
<li><p>Statistical Parity</p>
</li>
</ul>
</li>
</ul>
<p>AIES centered around bias mitigation, particularly around analyzing datasets to uncover fairness, or lack of. The following is a graph I built from a prison dataset, analyzing the percentage of custody statuses by Gender.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764624281222/4f7c1c70-5aa2-4a6b-a20d-280cabf340bf.png" alt="" style="display:block;margin:0 auto" />

<p>For my final paper, I analyzed a piece of research that compared LLM hiring decisions across different LLM models, namely they compared a proprietary in-house supervised trained model with other big generic LLMs like GPT and Claude.</p>
<div>
<div>💡</div>
<div><strong>Takeaway: </strong>This course highlights important externalities that come with widespread internet usage, particularly in a time of booming Large Language Models that vacuum up every bit of data in it’s path. As developers, we are trusted to design software that accurately reflect normative behavior and conversely mitigate non-normative behavior and bias.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score</strong>: 7/10 I enjoyed this class, it is not challenging but does get your gears turning on the impact software and data design can have. Data is my professional niche; it’s immensely important negative externalities are constantly and closely examined.</div>
</div>

<p><code>Summer 2025</code></p>
<hr />
<h3>NLP - Natural Language Processing</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>RNN</p>
</li>
<li><p>Transformers</p>
</li>
<li><p>Information Retrieval</p>
</li>
<li><p>Open Domain Question Answering</p>
</li>
<li><p>Machine Extraction</p>
</li>
<li><p>Machine Reading</p>
</li>
<li><p>Machine Translation</p>
<ul>
<li>Early Seq2Seq Models, Phrase Lattices and Transformers</li>
</ul>
</li>
<li><p>Privacy &amp; Societal Impacts</p>
<ul>
<li>Differential Privacy, Clipping Gradients</li>
</ul>
</li>
</ul>
<div>
<div>💡</div>
<div><strong>Takeaway:</strong> NLP is an extremely relevant course in 2026 with the explosion of LLMs. This course was fascinating to understand how LLMs function at their core. I appreciated the structure of the course starting from earlier language model architectures that are no longer used but laid the ground for LLMs like GPT. You sometimes hear LLMs are just supercharged predictive machines, constantly predicting the next token, in this course you actually understand how those predictions are made. I don’t believe this course was surface level either, the coding had bits and pieces of building language models from scratch but also introducing popular NLP python libraries. The course really tested your conceptual understanding as well, as the exams and quizzes were extremely tough in this regard.</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score</strong>: 9/10 There are some modules that could be re-worked but Professor Riedl is an excellent teacher and most of the lectures are designed extremely well. I thought this was one of the best courses for learning. I did think some of the coding exercises were a little too hand-holding at the start of the semester but I ultimately think everything was rounded out nicely.</div>
</div>

<p><code>Fall 2025</code></p>
<hr />
<h3>AI - Artificial Intelligence</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>A*</p>
</li>
<li><p>Decision Trees</p>
</li>
<li><p>Bayes Net</p>
</li>
<li><p>Clustering</p>
</li>
<li><p>Constraint Satisfaction Problems</p>
</li>
<li><p>Hidden Markov Models</p>
</li>
</ul>
<p>AI is <strong>ALL</strong> about writing code from scratch! A lot of these algorithms and concepts I was exposed to in prior classes. The coding exercises were very challenging but often felt like busy work without deep insightful learning.</p>
<p>The exams were fun to study for and take, although they felt more like SAT/ACT style word problems than a computer science class.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61dee6614e70945324c59e25/be7767ae-df60-406a-b42c-41854ea29480.png" alt="" style="display:block;margin:0 auto" />

<div>
<div>💡</div>
<div><strong>Takeaway:</strong> Old, specific concepts and algorithms continue to be extremely useful in modern software. The debugger tool has never been more useful :).</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score</strong>: 7/10 The core of the class was writing algorithms. I found most of the assignments challenging and rewarding. However, there was little connection or engagement to real-world scenarios. It just a rather dry class. The most connection and engagement was actually on the exams, where the material was turned into abstract word problems - with no coding required.</div>
</div>

<p><code>Spring 2026</code></p>
<hr />
<h3>CLAW - Computer Law</h3>
<p><strong>Core Themes</strong></p>
<ul>
<li><p>Intellectual Property</p>
<ul>
<li>Patents, Copyrights &amp; Trade Secrets</li>
</ul>
</li>
<li><p>Acquisitions</p>
</li>
<li><p>Licensing</p>
</li>
<li><p>Data Privacy</p>
</li>
</ul>
<p>The lectures were engaging; the professors clearly cared about crafting thoughtful presentations of the material. The class itself is not very difficult and didn't require a ton of hours for a summer class.</p>
<p>I enjoyed the vague, broad requirements of the two big projects i) Acquisition and ii) Code Analysis. In the Acquisition project, we were required to pick two companies, a target and an acquirer. We then had to do proper due diligence on the target company in the context of assessing their intellectual property. In the Code Analysis project we were tasked with analyzing two IDEs and whether or not there was copyright or other intellectual property infringement.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61dee6614e70945324c59e25/6894257d-f699-4642-afac-bb4427571f24.png" alt="" style="display:block;margin:0 auto" />

<div>
<div>💡</div>
<div><strong>Takeaway:</strong> Computer Law is also an extremely relevant course in 2026. The majority of patents nowadays are related to software or some form of digital intellectual property. This class has helped me add legal context to my current role with the software we're developing. This course also actually made me (briefly) consider going to law school!</div>
</div>

<div>
<div>🔥</div>
<div><strong>Score</strong>: 10/10 I really enjoyed the course. Engaging, thoughtful, relevant material. Not terribly challenging but deliberate projects and quizzes.</div>
</div>

<p><code>Summer 2026</code></p>
]]></content:encoded></item><item><title><![CDATA[How to Manage Consent in Google Tag Manager]]></title><description><![CDATA[Cookiebot in conjunction with Google Tag Manager makes consent management incredibly easy. Instructions to get started with Cookiebot and GTM linked here.
In this article I want to specifically cover nuances with the Cookiebot GTM template and how to...]]></description><link>https://datatribute.com/how-to-manage-consent-in-google-tag-manager</link><guid isPermaLink="true">https://datatribute.com/how-to-manage-consent-in-google-tag-manager</guid><category><![CDATA[#gdpr]]></category><category><![CDATA[google tag manager]]></category><category><![CDATA[gtm]]></category><category><![CDATA[analytics]]></category><category><![CDATA[Google Analytics]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Thu, 12 Dec 2024 23:41:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RwjciZ9JEfg/upload/21c378d282760d157caeb64611e10b4a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cookiebot in conjunction with Google Tag Manager makes consent management incredibly easy. Instructions to get started with Cookiebot and GTM linked <a target="_blank" href="https://support.cookiebot.com/hc/en-us/articles/360003793854-Google-Tag-Manager-deployment">here</a>.</p>
<p>In this article I want to specifically cover nuances with the Cookiebot GTM template and how to control opt-in vs. opt-out strategies.</p>
<h3 id="heading-opt-in-vs-opt-out">Opt-in vs. Opt-out</h3>
<p><strong>Opt-in</strong> indicates consent is not granted by default upfront. That is, until a user opts-in to tracking, they are not being tracked. In a real word opt-in configuration, a user visits a webpage for the first time, their consent settings are <strong>not</strong> initially granted by default. They are shown a consent banner and refuse to interact with the consent banner as they navigate the site. During this time, they are <strong>not</strong> tracked given the opt-in configuration.</p>
<p><strong>Opt-out</strong> indicates consent is granted by default upfront. That is, until a user opts-out of tracking, they are being tracked. In a real world opt-out configuration, a user visits a webpage for the first time, their consent settings are initially granted by default. They are shown a consent banner and refuse to interact with the consent banner as they navigate the site. During this time, they are being tracked given the opt-out configuration.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Type</strong></td><td><strong>Default Consent</strong></td><td><strong>Implication</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Opt-in</td><td>Denied ❌</td><td>Not tracked until action</td></tr>
<tr>
<td>Opt-out</td><td>Granted ✔️</td><td>Tracked until action</td></tr>
</tbody>
</table>
</div><p><a target="_blank" href="https://www.cookiebot.com/en/cookie-consent/"><em>Further Cookie Consent Documentation</em></a></p>
<h3 id="heading-controlling-opt-in-vs-opt-out-in-gtm">Controlling Opt-in vs. Opt-out in GTM</h3>
<p>Within the GTM Cookiebot template, there are options to control opt-in vs. opt-out strategies by taking advantage of the Regions parameter. This parameters allow a change in the default consent settings by region. If a region is not listed, all default consent settings are set to Denied.</p>
<p>In the example below, I’ve added US and changed all of the default consent states to Granted. In this instance, US users will be configured to an opt-out policy given they are initially tracked until taking action to opt-out and change their consent settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734036608430/81ea2df6-b497-43b8-bbbb-eacee1abf880.png" alt class="image--center mx-auto" /></p>
<p>This configuration, or any configuration can be tested and viewed within the GTM debugger. My physical location is the US, in the debugger, the on-page defaults are all granted as modified by the Region parameter.</p>
<p><strong>US IP Example - Initial Site Load:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734047109537/0c04dcca-6930-49a9-8e95-860762ae193c.png" alt class="image--center mx-auto" /></p>
<p>I connected to a IP via a VPN to a location in Denmark, an EU country. Now, the on-page defaults are Denied and continues to be Denied because I have not taken any action on the consent banner…yet.</p>
<p><strong>Denmark IP Example - Initial Site Load:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734047122234/563e5fff-270f-4767-8d87-e33a2ffe3f87.png" alt class="image--center mx-auto" /></p>
<p><strong>Denmark IP Example - NO - Interaction with Consent Banner:</strong></p>
<p><em>The current state gets populated a few milliseconds after the on-page defaults propagate, specifically for the event below, the</em> <code>gtm.load</code> <em>event. At this stage, the consent banner has been displayed to the user.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734047084302/3b51c4e7-89e4-4455-9c6f-8ec0295021a9.png" alt class="image--center mx-auto" /></p>
<p><strong>Denmark IP Example - Allow All Cookies - Interaction with Consent Banner:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734047072862/84a113fa-6286-4aca-9e96-81764b56fef7.png" alt class="image--center mx-auto" /></p>
<p>After allowing all cookies via the consent banner, Cookiebot pushes a custom event <code>cookie_consent_update</code> that changes the consent settings all to Granted. This is the dataLayer push event in the above screenshot. Point-forward the consent settings are saved in the user’s browser. This is an important step to understand because this is why all tags that fire would normally fire on page load need to be changed to fire on the custom event of <code>cookie_consent_update</code></p>
<p>Otherwise in this Denmark scenario, the Gtag or any other tags that fire upon page load/page view would not fire because they would initially be blocked by the default consent settings. Once the user takes action to change settings (via consent banner), the custom event <code>cookie_consent_update</code> is pushed to the dataLayer and appropriate tags can trigger off of that custom event. On subsequent page loads after consent action has been taken, Cookiebot continues to fire a <code>cookie_consent_update</code> which applies the saved settings of the user. This ensures the Gtag continues to fire on subsequent page loads (even when the consent banner ceases to be shown).</p>
<h3 id="heading-more-examples">More Examples</h3>
<ul>
<li><p><strong>US</strong> user visits <em>datatribute.com</em> for the first time -&gt; Piwik Pro, GA4 etc <strong>is</strong> initially triggered and data captured -&gt; User <strong>DENYS</strong> tracking via consent banner -&gt; User navigates to another page -&gt; Piwik, GA4 etc is <strong>NOT</strong> triggered.</p>
</li>
<li><p><strong>US</strong> user visits <em>datatribute.com</em> for the first time-&gt; Piwik, GA4 etc <strong>is</strong> initially triggered and data captured -&gt; User <strong>ACCEPTS</strong> all tracking via consent banner -&gt; User navigates to another page -&gt; Piwik, GA4 etc <strong>is</strong> triggered.</p>
</li>
<li><p><strong>Denmark</strong> user visits <em>datatribute.com</em> for the first time -&gt; Piwik, GA4 etc is <strong>NOT</strong> triggered -&gt; Consent banner is shown -&gt; User <strong>ACCEPTS</strong> all tracking via banner -&gt; Consent Update event is triggered -&gt; Piwik, GA4 etc is triggered for initial page -&gt; User navigates to another page -&gt; Piwik, GA4 etc is triggered.</p>
</li>
</ul>
<p>These examples behave this way because of the opt-in vs. opt-out policies controlled by the Region parameter.</p>
<h3 id="heading-other-notes">Other Notes</h3>
<ul>
<li><p>Region codes are ISO-3166 A-2 codes. Full list <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes">here</a>.</p>
</li>
<li><p>Regions that share the same settings can be combined to a comma separated string like <code>US,CA,UK,IT</code></p>
</li>
<li><p>In the context of regions, Cookiebot checks for the users physical location via IP origination</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Advanced Web Performance Tracking with GTM - Google Tag Manager]]></title><description><![CDATA[GTM is a powerful free tool and most organizations are not using GTM to its fullest potential. Below are a few brief examples of how I've implemented advanced custom tracking. These tags, variables and triggers have contributed to a better analytical...]]></description><link>https://datatribute.com/advanced-web-performance-tracking-with-gtm-google-tag-manager</link><guid isPermaLink="true">https://datatribute.com/advanced-web-performance-tracking-with-gtm-google-tag-manager</guid><category><![CDATA[gtm]]></category><category><![CDATA[google tag manager]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[ga4]]></category><category><![CDATA[analytics]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Tue, 02 May 2023 17:05:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688220559835/9d51f6d7-c0bf-40fe-914e-02ae567d8587.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GTM is a powerful free tool and most organizations are not using GTM to its fullest potential. Below are a few brief examples of how I've implemented advanced custom tracking. These tags, variables and triggers have contributed to a better analytical understanding of a website's performance.</p>
<hr />
<h2 id="heading-custom-dimensions-page-metadata-with-custom-javascript">Custom Dimensions - Page Metadata with Custom JavaScript</h2>
<p>Large websites have categories or groupings of pages and sometimes they don't always follow the same ULR pattern, otherwise analyzing by /blog/content-group1/mypost123 would be easy.</p>
<p>There is often a ton of metadata contained within a CMS and little of is exposed directly on the webpage, either visibly to the end-user or in the URL pattern but this data is still useful for analysis!</p>
<p>In GTM, I've implemented custom JavaScript to pickup meta tags in the page's HTML. This Custom JavaScript variable can then be used to add as a custom dimension in the page view tag.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPageSubType</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">return</span> <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'meta[name="analytics-subtype"]'</span>).content;
}
</code></pre>
<p>This is another example that is a little more complex but my client wanted to analyze blog post author performance within Google Analytics. This variable grabs onto an element that contains the author's information. There is some logic in this to account for multiple and single-authored blog posts.</p>
<pre><code class="lang-javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMeta</span>(<span class="hljs-params"></span>) </span>{

  authors = <span class="hljs-built_in">document</span>.getElementsByName(<span class="hljs-string">'cludo:authors'</span>);

  <span class="hljs-keyword">if</span> (authors.length == <span class="hljs-number">1</span>) 
      {
      <span class="hljs-keyword">return</span> authors[<span class="hljs-number">0</span>].getAttribute(<span class="hljs-string">'content'</span>);
      }

  <span class="hljs-keyword">else</span> 
  {
  authors_string= <span class="hljs-string">''</span>; 
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i =<span class="hljs-number">0</span>; i &lt; authors.length; i++)
            {
                <span class="hljs-keyword">if</span> (i != authors.length <span class="hljs-number">-1</span>) <span class="hljs-comment">//if doesn't equals last iteration, add spacer bt authors</span>
                {
                  authors_string += authors[i].getAttribute(<span class="hljs-string">'content'</span>) + <span class="hljs-string">", "</span>;
                }
                  <span class="hljs-keyword">else</span> 
                {
                 authors_string += authors[i].getAttribute(<span class="hljs-string">'content'</span>);
                }

              }
   <span class="hljs-keyword">return</span> authors_string;
  }
}
</code></pre>
<h2 id="heading-lookup-tables-andamp-custom-triggers-analyze-hotspots">Lookup Tables &amp; Custom Triggers - Analyze Hotspots</h2>
<p>GTM lookup tables take an input and output a result based on what the input matches. It's a fairly simple logic table that could be accomplished in a Custom JavaScript variable but this built-in variable type is much cleaner and easier to read for most folks.</p>
<p>This specific variable takes in a click class upon the user clicking, depending on where they click on the homepage, those specific links are recorded and segmented by 3 different types.</p>
<p>This allowed my client to analyze which featured links on the homepage were the most popular.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688218790406/b20d1de0-690c-4d50-bc8c-a7486426ed73.png" alt class="image--center mx-auto" /></p>
<p>This is another example that utilizes a trigger to specifically track clicks on the favorite button of articles. This trigger then fires the favorite_this tag that funnels a custom event to GA4.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688219432267/8c4e8fa7-776c-4c32-b73f-4ad958324aba.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-timers-analyze-time-on-page">Timers - Analyze Time on Page</h2>
<p>With the release of GA4, it's trickier to analyze time on page performance as we were all used to having as a default metric in UA.</p>
<p>GTM timer trigger types allow events to fire at certain intervals. For instance, the example below fires on all pages (Regex matches all) every 15 seconds.</p>
<p>You can also couple these triggers types with form fills, purchase funnels and any other interaction where it's insightful to see how long users take to perform an action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688219902016/07917a2f-bffe-4e37-9d44-ea3a4ead9ef2.png" alt class="image--center mx-auto" /></p>
<hr />
<p>This is just scratching the surface of what GTM can do! Some might question why GTM is needed when web developers could accomplish all of this by sending custom dataLayer events. That is true however, the power in GTM allows fast iteration and deployment of purely analytical initiatives that are decoupled from the main site's deployment. If you want to learn more about GTM let me know!</p>
]]></content:encoded></item><item><title><![CDATA[Tracking a Job Change with UTMs and Resume Hyperlinks]]></title><description><![CDATA[Thought
I got curious if recruiters or hiring managers were even reading my resume, let alone clicking on the links I provided them on my resume. So I got curious.. how can I get concrete visibility into their engagement?
Here is what the top of my r...]]></description><link>https://datatribute.com/tracking-a-job-change-with-utms-and-resume-hyperlinks</link><guid isPermaLink="true">https://datatribute.com/tracking-a-job-change-with-utms-and-resume-hyperlinks</guid><category><![CDATA[resume]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[utm]]></category><category><![CDATA[data analysis]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Sun, 23 Apr 2023 17:22:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/sirnsSiXRWM/upload/8cb4daac9d4a3ccbe8edb46625c22e8e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-thought">Thought</h2>
<p>I got curious if recruiters or hiring managers were even reading my resume, let alone clicking on the links I provided them on my resume. So I got curious.. how can I get concrete visibility into their engagement?</p>
<p>Here is what the top of my resume looks like</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682194887694/2e90d182-0af4-4fa5-9bba-12f5e9adc8bc.png" alt class="image--center mx-auto" /></p>
<p>I came up with the thought that I could append UTMs to each of these links for different respective companies. I also figured I could do it programmatically instead of creating a new PDF for each company and manually editing the hyperlinks.</p>
<h2 id="heading-first-step-create-a-company-tracking-list">First Step -- Create a Company Tracking List</h2>
<p>I started a CSV file that looks like this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Company</td><td>campaign</td><td>source</td><td>medium</td></tr>
</thead>
<tbody>
<tr>
<td>Blizzard</td><td>job_search_mar22</td><td>resume_ref_v1</td><td>blizzard</td></tr>
<tr>
<td>Anduril</td><td>job_search_mar22</td><td>resume_ref_v1</td><td>anduril</td></tr>
<tr>
<td>Boston Dynamics</td><td>job_search_mar22</td><td>resume_ref_v1</td><td>boston_d</td></tr>
</tbody>
</table>
</div><p>It's not too much work to maintain this list, I updated it every time I was getting ready to apply for a job.</p>
<h2 id="heading-next-step-create-a-python-script">Next Step -- Create a Python Script</h2>
<ul>
<li><p>Loop over CSV records from the CSV tracker list</p>
</li>
<li><p>Read my current resume and extract all hyperlinks using the <code>pdfrw</code> library</p>
</li>
<li><p>Loop over all companies and modify all hyperlinks</p>
</li>
<li><p>Save a new PDF for each company</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pdfrw
<span class="hljs-keyword">import</span> csv

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_csv_records</span>(<span class="hljs-params">company_file</span>):</span>

    <span class="hljs-keyword">with</span> open(company_file, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> csv_file:
        csv_reader = csv.DictReader(csv_file)
        <span class="hljs-keyword">return</span> [row <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> csv_reader]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_pdf</span>(<span class="hljs-params">row,resume_base_file</span>):</span>

    <span class="hljs-comment"># Load the PDF file</span>
    base_pdf = pdfrw.PdfReader(resume_base_file)
    page = base_pdf.pages[<span class="hljs-number">0</span>]

    <span class="hljs-comment"># Create an empty PDF file</span>
    new_pdf = pdfrw.PdfWriter()

    <span class="hljs-comment"># Check if the page has any hyperlinks</span>
    <span class="hljs-keyword">for</span> annot <span class="hljs-keyword">in</span> page.Annots <span class="hljs-keyword">or</span> []:
        old_url = annot.A.URI
        <span class="hljs-keyword">if</span> old_url == <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">continue</span>
        <span class="hljs-comment"># Replace the old path with the new path</span>
        old_url = str(old_url).strip().replace(<span class="hljs-string">'('</span>,<span class="hljs-string">''</span>).replace(<span class="hljs-string">')'</span>,<span class="hljs-string">''</span>)

        new_url = (
            <span class="hljs-string">f'(<span class="hljs-subst">{old_url}</span>\n'</span>
            <span class="hljs-string">f'?utm_campaign=<span class="hljs-subst">{row[<span class="hljs-string">"campaign"</span>]}</span>\n'</span>
            <span class="hljs-string">f'&amp;utm_source=<span class="hljs-subst">{row[<span class="hljs-string">"source"</span>]}</span>\n'</span>
            <span class="hljs-string">f'&amp;utm_medium=<span class="hljs-subst">{row[<span class="hljs-string">"medium"</span>]}</span>)\n'</span>
            )
        <span class="hljs-comment"># Convert the new string for PDF strings</span>
        new_url = pdfrw.objects.pdfstring.PdfString(new_url)
        <span class="hljs-comment"># Update the hyperlink</span>
        annot.A.URI = new_url
    <span class="hljs-comment"># Add the modified page to the new PDF file</span>
    new_pdf.addpage(page)
    <span class="hljs-comment"># Save the modified PDF file</span>
    new_pdf.write(<span class="hljs-string">f'Resume_MyName_<span class="hljs-subst">{row[<span class="hljs-string">"Company"</span>].strip()}</span>.pdf'</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    resume_base_file = <span class="hljs-string">'resume_base.pdf'</span>
    company_file = <span class="hljs-string">'company_list.csv'</span>

    data = get_csv_records(company_file)

    <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> data:
        create_pdf(row,resume_base_file)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<p>I won't break down the code too much, it's fairly straightforward consisting of two core functions, reading in the CSV file and creating a new PDF for each record (Company) in the CSV.</p>
<p>Within the <code>create_pdf()</code> function I utilized the <code>pdfrw</code> library to read the base resume file, and create a new page object based on the base resume. Then we can extract all strings from the page using <code>page.Annots</code> , then we can see if any of those strings meet the criteria of a URI (a web address on the resume).</p>
<p>If the string is a URI, I do some light cleanup on the link and then append/construct the UTMs from the CSV record.</p>
<p>Lastly, the PDF is saved as a new PDF. Essentially we're modifying the base resume each time and then saving it under a different name.</p>
<h3 id="heading-before-executing">Before Executing</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682214679166/252b49ba-14d5-4665-b96d-ed0bbfbf960e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-after-executing">After Executing</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682214695742/5c984089-93ca-4f6b-ab9e-6c8de2bb822a.png" alt class="image--center mx-auto" /></p>
<p>I can now shoot off customized resumes to all respective companies. The resume companies will receive will have specific UTMs appended to all hyperlinks in my resume, I'll now be able to see if anyone from Blizzard looked at my resume vs. Anduril.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The totally overengineered result is a neat little Google Analytics 4 chart that I can track the analytics on for any user who clicks the link(s).</p>
<p>I'm able to drill into the detail of whom is looking at my resume, for how long, how often and if they navigate elsewhere within my site(s)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682270254454/b0b9d8bb-3605-41d5-be95-7e728bc84d4f.png" alt class="image--center mx-auto" /></p>
<p>If you'd like to set this up on your own, just remember you need a GA4 property on a website that you own and of course, you need access to that property. In my case, I am also appending UTMs to my Github profile but I'll never be able to see those metrics because I don't own that site of course. In contrast, I have full analytics visibility into my Portfolio site and this Hashnode Blog.</p>
<p>Thanks for reading! Feel free to ask questions if you'd like to know more!</p>
]]></content:encoded></item><item><title><![CDATA[Streamlining Your Analytics Pipeline: Exporting GA4 Data to BigQuery, Running DBT Models, and Visualizing with Looker Studio]]></title><description><![CDATA[Problem
GA4 is great until it isn't. There are 2 big issues with standard (free) GA4, especially if you use Looker Studio:

GA4 API Quota Limits

14-Month Data Retention Policy



GA4 API Quota Limits
If your organization accesses GA4 data through Lo...]]></description><link>https://datatribute.com/streamlining-analytics-pipeline</link><guid isPermaLink="true">https://datatribute.com/streamlining-analytics-pipeline</guid><category><![CDATA[looker studio]]></category><category><![CDATA[ga4]]></category><category><![CDATA[dbt]]></category><category><![CDATA[Pipeline]]></category><category><![CDATA[analytics]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Sat, 22 Apr 2023 16:25:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/0_32AIxy1Hk/upload/8bdd6b50fd45ec7c9d82d0260be8b204.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-problem">Problem</h1>
<p>GA4 is great until it isn't. There are 2 big issues with standard (free) GA4, especially if you use Looker Studio:</p>
<ul>
<li><p><strong>GA4 API Quota Limits</strong></p>
</li>
<li><p><strong>14-Month Data Retention Policy</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-ga4-api-quota-limits">GA4 API Quota Limits</h2>
<p>If your organization accesses GA4 data through Looker Studio you have probably encountered this error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682175852119/e945c418-4c09-4b71-bf58-167d90cadf03.png" alt class="image--center mx-auto" /></p>
<p>Accessing <a target="_blank" href="https://developers.google.com/analytics/devguides/reporting/data/v1/quotas?hl=en">GA4's API</a> via Looker Studio is limited by tokens, there is a certain allowable amount. Tokens are consumed each time a chart or visualization is loaded, pivoted or filtered.</p>
<p>There is <em>some</em> caching that is happening behind the scenes but if you are a power user of Looker Studio and or have multiple stakeholders accessing reports, you can very quickly run into these limits, halting your analytic analyses.</p>
<h3 id="heading-standard-ga4-limits">Standard GA4 Limits</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Frequency</td><td>Quota Name</td><td>Allowed Tokens</td></tr>
</thead>
<tbody>
<tr>
<td>Hourly</td><td>Per Property Per Project</td><td>1,750</td></tr>
<tr>
<td>Hourly</td><td>Per Property</td><td>5,000</td></tr>
<tr>
<td>Daily</td><td>Per Property</td><td>25,000</td></tr>
</tbody>
</table>
</div><p>In other words, a single GA4 property is only allowed 5,000 tokens per hour. Loading 1 page of 5 visualizations with basic complexity in Looker consumes about ~500 tokens. Your token usage will vary greatly with the number of page objects and the complexity of those queries.</p>
<hr />
<h2 id="heading-14-month-data-retention-policy">14-Month Data Retention Policy</h2>
<p>Standard GA4 has a data retention policy of 14 months. That's to say only 14 months' worth of data is stored on your GA4 instance.</p>
<p>After that period, unless exported, the data is deleted forever...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682176599892/b8d053d9-b351-436d-9464-6ed61b8dfa52.gif" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-what-about-analytics-360">What About Analytics 360..</h2>
<p>Upgrading to 360 has several advantages but in regards to these 2 issues:</p>
<ul>
<li><p>All Quotas are increased by 10x (<strong>1,750</strong> tokens per hour to <strong>17,500</strong>)</p>
</li>
<li><p>Data Retention is increased from <strong>14</strong> to <strong>50</strong> months</p>
</li>
</ul>
<p>However.. Analytics 360 is a whopping $50,000 per year. So onto another option for most of us..</p>
<hr />
<h1 id="heading-solution-high-level-steps">Solution - High-Level Steps</h1>
<h3 id="heading-exporting-ga4-data-to-bigquery"><strong>Exporting GA4 data to BigQuery</strong></h3>
<ul>
<li><p>Set up a BigQuery project and dataset.</p>
</li>
<li><p>Link your GA4 property to your BigQuery project.</p>
</li>
<li><p>Choose which GA4 data you want to export to BigQuery.</p>
</li>
<li><p>Schedule regular data exports to keep your data up-to-date.</p>
</li>
</ul>
<h3 id="heading-running-dbt-models">Running DBT Models</h3>
<ul>
<li><p>Create a Cloud DBT account and connect it to your BigQuery project.</p>
</li>
<li><p>Copy or Write DBT models in SQL to transform your GA4 data into a format that is more conducive to analysis.</p>
</li>
<li><p>Use DBT to automate the data transformation process.</p>
</li>
<li><p>Test your DBT models to ensure they are working correctly.</p>
</li>
</ul>
<h3 id="heading-visualizing-with-looker-studio">Visualizing with Looker Studio</h3>
<ul>
<li><p>Connect Looker Studio to your BigQuery project.</p>
</li>
<li><p>Create custom dashboards and visualizations to display your data.</p>
</li>
</ul>
<hr />
<h1 id="heading-disclaimers">Disclaimers</h1>
<h3 id="heading-1-million-event-export-limit">1 Million Event Export Limit</h3>
<p>Google allows standard GA4 properties to export <strong>1M</strong> events per day, if you are below this threshold, the export process is free.</p>
<h3 id="heading-costs">Costs</h3>
<p>There will be incremental <strong>storage</strong> costs and <strong>querying</strong> costs associated with Big Query. However, costs should be fairly nominal, a few hundred dollars a month, for your average-sized organization. See <a target="_blank" href="https://cloud.google.com/products/calculator">Google Cloud's Cost Estimator</a> and my other [future] article about estimating Big Query costs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682180984641/8931a1f8-d2a2-419e-96eb-fbe03cc992f7.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-google-subject-to-change">Google - Subject to Change</h3>
<p>A lot of folks are upset about the token limitation. Google is subject to change its terms and product offerings at any time. They have already increased the token limit once with the initial outcry and I believe their native solution to this, the Extract Data Source, could get better in time.</p>
<p>But.. not a bad idea to get your exports going regardless so you can retain a longer history of your web analytics.</p>
<h3 id="heading-other-solutions">Other Solutions</h3>
<p>There are certainly other solutions to GA4's quota limits. Some involve paying another vendor like Power My Analytics, using Looker's Extract layer or transitioning to native GA4 reports.</p>
<p>All are valid solutions. However, unless you pay for 360, there is no other solution to retain your data. With that, adding a DBT layer isn't too much work. If you're already exporting data to Big Query you might as well do something with it.</p>
<p>All that said, I do believe this may not fit everyone's use case or technical aptitude. I plan on doing another article that lays out other options.</p>
<hr />
<h1 id="heading-why-dbt">Why DBT?</h1>
<p>The reason we need to use DBT is because the native export that comes out of GA4 is incredibly nested. The data needs to go through another step to be usable in analysis.</p>
<p>There <em>can</em> be costs associated with DBT, but you can get away with the free tier depending on your needs. The process can all be done within the free DBT tier, the advantages to an upgraded plan are more organizationally related (more user accounts, access control etc.) at least for this process specifically.</p>
<hr />
<h1 id="heading-conclusion">Conclusion</h1>
<p>We didn't get too into the weeds on setting up DBT or a GA4 export. Those detailed steps deserve another dedicated write-up. However, I hope this enlightened you and at the very least, got you thinking about taking action with your GA4 data.</p>
<p>More to come! Please leave a comment if you found this helpful or would like to know more!</p>
]]></content:encoded></item><item><title><![CDATA[Will SQL Ever Die?]]></title><description><![CDATA[Longer Answer:
SQL (Structured Query Language) has been around for over 40 years and remains one of the most widely used programming languages for managing and analyzing data. With the rise of big data, data science, and machine learning, some have s...]]></description><link>https://datatribute.com/will-sql-ever-die</link><guid isPermaLink="true">https://datatribute.com/will-sql-ever-die</guid><category><![CDATA[SQL]]></category><category><![CDATA[data-engineering]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 24 Feb 2023 19:36:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ir5gC4hlqT0/upload/2ee50b738f8e3d748d189178bf92cef2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Longer Answer:</p>
<p>SQL (Structured Query Language) has been around for over 40 years and remains one of the most widely used programming languages for managing and analyzing data. With the rise of big data, data science, and machine learning, some have speculated that SQL may become obsolete. However, the reality is that SQL continues to evolve and adapt to new technologies, making it a valuable skill for anyone working with data. In this article, I'll explore whether SQL will ever die and why it remains relevant today.</p>
<p>First, it's important to understand that SQL is a standard programming language for managing relational databases. It allows users to create, read, update, and delete data from a database using a set of commands that are easy to understand and use. SQL is supported by all major database vendors and can be used with a wide range of programming languages, making it a versatile tool for managing data.</p>
<p>One of the reasons why SQL has remained relevant is its ability to handle structured data. Structured data refers to data that is organized in a consistent and structured format, such as a spreadsheet or a database. While unstructured data, such as social media posts or images, has become increasingly important, structured data remains the foundation of most data-driven applications. SQL excels at managing structured data and can be used to query and manipulate large datasets quickly and efficiently.</p>
<p>Another reason why SQL remains relevant is its ability to integrate with other technologies. SQL is used in conjunction with a wide range of programming languages, including Python, R, and Java. This means that developers can use SQL to manage data and then use other languages to perform analysis or build applications. SQL can also be used in conjunction with big data technologies, such as Hadoop and Spark, making it a valuable tool for managing large-scale data processing.</p>
<p>Finally, SQL continues to evolve to meet the needs of modern data management. New features, such as JSON support and columnar storage, have been added to SQL databases to improve performance and support new data types. Additionally, cloud-based databases, such as Amazon Web Services (AWS) and Microsoft Azure, have made it easier to deploy and manage SQL databases in the cloud.</p>
<p>In conclusion, SQL is not going anywhere anytime soon. While new technologies may emerge that challenge its dominance, SQL remains a versatile and reliable tool for managing structured data. Its ability to integrate with other technologies and its continued evolution ensure that it will remain a valuable skill for anyone working with data. Whether you're a data analyst, a software developer, or a data scientist, learning SQL is a smart investment in your career.</p>
]]></content:encoded></item><item><title><![CDATA[Automating My Car Search - Python & GitHub Actions]]></title><description><![CDATA[I was rear-ended a few months ago, luckily no one was hurt. However, the car was totaled and it was time to search for a vehicle in a tough car market. There was very limited inventory everywhere due to COVID and folks were flush with cash. High dema...]]></description><link>https://datatribute.com/automating-my-car-search-python-github-actions</link><guid isPermaLink="true">https://datatribute.com/automating-my-car-search-python-github-actions</guid><category><![CDATA[serverless]]></category><category><![CDATA[Python]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Sat, 28 Jan 2023 18:49:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/MAgPyHRO0AA/upload/cd38f3a0a55a1468b475f54b7cfc1455.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was rear-ended a few months ago, luckily no one was hurt. However, the car was totaled and it was time to search for a vehicle in a tough car market. There was very limited inventory everywhere due to COVID and folks were flush with cash. High demand and low supply.</p>
<p>I was scouring my local Toyota websites daily until I built the following solution to automatically scrape the listings and productionize the script with GitHub Actions.</p>
<h2 id="heading-goal">Goal</h2>
<ul>
<li><p>Scrape my local Toyota website for all RAV4 listings.</p>
</li>
<li><p>Send myself the prices of each car via SMS text message, sorted by lowest price to highest.</p>
</li>
</ul>
<h2 id="heading-purpose">Purpose</h2>
<ul>
<li>Monitor the inventory and fluctuating prices; allowing me to jump on a deposit for a car.</li>
</ul>
<h2 id="heading-python-procedure">Python Procedure</h2>
<h3 id="heading-identifying-html-values">Identifying HTML Values</h3>
<ul>
<li><p>First, I needed to find the HTML values I was after on my Toyota dealer's website.</p>
</li>
<li><p>I inspected the MSRP value on the inventory page and found</p>
<ul>
<li><code>&lt;div class = "price_value"&gt; $34,889</code></li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674927780448/40605f2f-7647-416a-9c26-172e590dd4d1.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-requests-and-beautiful-soup">Requests and Beautiful Soup</h3>
<p>Easy enough, the start of the script:</p>
<ul>
<li><p>Sends a basic get request</p>
</li>
<li><p>Creates a Beautiful Soup object</p>
</li>
<li><p>Grabs all <code>div</code> elements with the <code>class</code> name of <code>price_value</code></p>
</li>
<li><p>De-duplicate the returned items using the <code>set()</code> data structure</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_bike_items</span>(<span class="hljs-params">url</span>):</span>
    <span class="hljs-comment">#Use the requests library for a simple get request</span>
    response = requests.get(url)

    <span class="hljs-comment">#Build the bs4 soup object</span>
    html_soup = BeautifulSoup(response.text, <span class="hljs-string">'html.parser'</span>)

    <span class="hljs-comment">#Find all divs with a name of </span>
    all_cars = html_soup.find_all(<span class="hljs-string">'div'</span>, class_ = <span class="hljs-string">'price_value'</span>)
    all_cars = set(all_cars)
</code></pre>
<p>Some light cleaning is needed for the returned strings</p>
<ul>
<li><p>Loop through all cars</p>
</li>
<li><p>Replace new lines and strip white space</p>
</li>
<li><p>Append back to a new list</p>
</li>
<li><p>Sort list</p>
</li>
<li><p>Return the list</p>
</li>
</ul>
<h3 id="heading-light-cleaning">Light Cleaning</h3>
<pre><code class="lang-python">    cars_text = []
    <span class="hljs-keyword">for</span> cars <span class="hljs-keyword">in</span> all_cars:
        text = cars.text
        care_c = text.replace(<span class="hljs-string">"\n"</span>,<span class="hljs-string">""</span>).strip()
        cars_text.append(care_c)

    cars = sorted(cars_text)
    <span class="hljs-keyword">return</span> cars
</code></pre>
<h3 id="heading-constructing-string-message">Constructing String Message</h3>
<p>I had two URLs to pass into this function, one for Hybrid and one for Regular gasoline RAV4s but I wanted each page to be sent in one text message</p>
<ul>
<li><p>Loop over array of URLs</p>
</li>
<li><p>The first URL in the list was for Hybrid RAV4s so I added REG to the end of the first item in the array (index 0) and started <code>sms_text</code> with 'HYBRIDS || '</p>
</li>
<li><p>Join the returned list of strings into one string, separated by <code>||</code></p>
<ul>
<li><code>'||'.join(get_bike_items(url))</code></li>
</ul>
</li>
</ul>
<pre><code class="lang-python">urls = [<span class="hljs-string">'https://www.sheehytoyotafredericksburg.com/new-toyota-rav4-hybrid-fredericksburg-va'</span>,
<span class="hljs-string">'https://www.sheehytoyotafredericksburg.com/new-toyota-rav4-fredericksburg-va'</span>]

sms_text = <span class="hljs-string">'HYBRIDs || '</span>

<span class="hljs-keyword">for</span> index, url <span class="hljs-keyword">in</span> enumerate(urls):
    <span class="hljs-keyword">if</span> index ==<span class="hljs-number">0</span>:
        sms_text += <span class="hljs-string">'||'</span>.join(get_bike_items(url)) +<span class="hljs-string">'\nREG ||'</span>
    <span class="hljs-keyword">else</span>:
        sms_text += <span class="hljs-string">'||'</span>.join(get_bike_items(url))

<span class="hljs-comment">#Calling the function below to send the SMS </span>
send(sms_text)
</code></pre>
<h3 id="heading-sms-message">SMS Message</h3>
<p>Lastly, I just needed to send the SMS message!</p>
<ul>
<li>Below is the function for sending a SMS message. It works by using an email account to send the message to a phone carrier network</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> smtplib
carriers = {
    <span class="hljs-string">'att'</span>:    <span class="hljs-string">'@mms.att.net'</span>,
    <span class="hljs-string">'tmobile'</span>:<span class="hljs-string">' @tmomail.net'</span>,
    <span class="hljs-string">'verizon'</span>:  <span class="hljs-string">'@vtext.com'</span>,
    <span class="hljs-string">'sprint'</span>:   <span class="hljs-string">'@page.nextel.com'</span>
}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send</span>(<span class="hljs-params">message</span>):</span>
        <span class="hljs-comment"># Replace the number with your own, or consider using an argument\dict for multiple people.</span>
    to_number = <span class="hljs-string">'xxxxxxxxxx{}'</span>.format(carriers[<span class="hljs-string">'xxxxx'</span>])
    auth = (<span class="hljs-string">'xxx@gmail.com'</span>, <span class="hljs-string">'xxxpass'</span>)

    <span class="hljs-comment"># Establish a secure session with gmail's outgoing SMTP server using your gmail account</span>
    server = smtplib.SMTP( <span class="hljs-string">"smtp.gmail.com"</span>, <span class="hljs-number">587</span> )
    server.starttls()
    server.login(auth[<span class="hljs-number">0</span>], auth[<span class="hljs-number">1</span>])

    <span class="hljs-comment"># Send text message through SMS gateway of destination number</span>
    server.sendmail( auth[<span class="hljs-number">0</span>], to_number, message)
</code></pre>
<hr />
<h2 id="heading-github-actions-automation">GitHub Actions Automation</h2>
<p>After the script was running, I wanted to be able to send these messages to myself automatically twice a day.</p>
<p>GitHub actions performs very well for this type of use case. It's essentially a serverless framework for executing code. I plan to do another article that goes deeper into Actions.</p>
<p>For now, to get the above script running:</p>
<ul>
<li><p>Push code to a private GitHub repository</p>
</li>
<li><p>Create a <code>.yml</code> file in your repository with the path of <code>.github/workflows/main.yml</code></p>
</li>
</ul>
<p>The structure of the YAML is fairly straightforward</p>
<h3 id="heading-github-action-yaml-file">GitHub Action YAML File</h3>
<pre><code class="lang-yaml"><span class="hljs-comment">#Name of the Action</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">car_scrape_sms</span>

<span class="hljs-comment">#On is the trigger of when this will be executed</span>
<span class="hljs-attr">on:</span> 
<span class="hljs-comment">#Here I am using a basic cron schedule </span>
  <span class="hljs-attr">schedule:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">cron:</span> <span class="hljs-string">'15 14 * * *'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">cron:</span> <span class="hljs-string">'15 2 * * *'</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">upload:</span>
    <span class="hljs-comment">#The execution environment of the runner </span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Checking the repository out to the execution env </span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Python</span> 
        <span class="hljs-comment"># Installs Python on the env </span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">python-version:</span> <span class="hljs-string">'3.10'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-comment">#CLI arguments to execute on the runner </span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          pip install requests
          pip install beautifulsoup4
</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">run</span> <span class="hljs-string">main</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
          <span class="hljs-string">python</span> <span class="hljs-string">main.py</span>
</code></pre>
<h3 id="heading-github-action-keywords-explained">GitHub Action Keywords Explained</h3>
<ul>
<li><p><code>name:</code> is the name of your Action run</p>
</li>
<li><p><code>on:</code> is the keyword to trigger your workflow, here I am using a cron schedule</p>
</li>
<li><p><code>jobs:</code> starts the main entry into the workflow</p>
<ul>
<li><p><code>runs-on:</code> is the platform OS, essentially the container (runner) environment that your job will run on</p>
</li>
<li><p><code>steps:</code> are sections to categorically separate pieces of your Action</p>
<ul>
<li><p><code>name:</code> of your step</p>
</li>
<li><p><code>uses:</code> can call other actions</p>
<ul>
<li><p>Here I am using another action that checkouts my repository to the container environment that this will be executed on. Essentially it copies all of the scripts in the repo to the execution env.</p>
</li>
<li><p>I am also setting up python in the container env with <code>actions/setup-python@v3</code></p>
<ul>
<li><code>with:</code> is passing a parameter to specify the version of python from that above action</li>
</ul>
</li>
</ul>
</li>
<li><p><code>run:</code> here we can specify the commands to run on the runner. You can treat this as the command line of the runner.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><em>This barely scratches the surface of the power of GitHub Actions!!</em></p>
<h3 id="heading-action-run-example">Action Run Example</h3>
<p>Below is an example of an Action run for the above scripts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674931130820/2185a7e7-5023-4b7c-b6f2-bf93742e7aff.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-output">Output</h2>
<p>Finally here is the output that gets sent to my phone</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674931567630/0cbb03c8-3d98-4794-a83c-f75cfc383fba.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Top 5 Linux Commands I use as Data Engineer]]></title><description><![CDATA[I'm in and out of server boxes all day using terminals and screen sessions. Making edits to files, setting up cron jobs, creating docker containers and monitoring data pipelines. Below are the top 5 commands I use every day.
1
cd and cd ..

Navigate ...]]></description><link>https://datatribute.com/top-5-linux-commands-data-engineer</link><guid isPermaLink="true">https://datatribute.com/top-5-linux-commands-data-engineer</guid><category><![CDATA[Linux]]></category><category><![CDATA[terminal]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Tue, 20 Dec 2022 15:03:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671548572954/38AXeZh2f7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'm in and out of server boxes all day using terminals and screen sessions. Making edits to files, setting up cron jobs, creating docker containers and monitoring data pipelines. Below are the top 5 commands I use every day.</p>
<h3 id="heading-1">1</h3>
<h3 id="heading-cd-and-cd"><code>cd</code> and <code>cd ..</code></h3>
<ul>
<li><p>Navigate to a directory <code>cd MyFolder</code></p>
</li>
<li><p>Navigate back a directory relative to your current path. If current path is <code>/home/user/MyFolder</code> executing <code>cd ..</code> will bring you to <code>/home/user</code></p>
</li>
</ul>
<h3 id="heading-2">2</h3>
<h3 id="heading-ls-and-ls-ls-h"><code>ls</code> and <code>ls -ls -h</code></h3>
<ul>
<li>List all contents in current path. <code>ls</code> executed while on path <code>./MyDirectory</code> will list <code>main.py</code>, <code>profile_pic.jpg</code> and <code>creds.txt</code></li>
</ul>
<pre><code class="lang-python-repl">├── MyDirectory
│   ├── main.py
│   └── profile_pic.jpg
    └── creds.txt
</code></pre>
<ul>
<li>List all contents and it's attributes in current path AND in human-readable (<code>-h)</code> format. ie. <code>main.py</code> is 903 bytes in size using <code>ls -ls -h</code></li>
</ul>
<pre><code class="lang-python-repl">-rw-rw-r-- 1 adam adam   0 Dec 20 09:49 creds.txt
-rw-rw-r-- 1 adam adam 903 Dec 20 09:49 main.py
-rw-rw-r-- 1 adam adam   0 Dec 20 09:49 profile_pic.jpg
</code></pre>
<h3 id="heading-3">3</h3>
<h3 id="heading-pwd"><code>pwd</code></h3>
<ul>
<li>Prints the full name of the path in the current working directory. <code>pwd</code> while in <code>MyDirectory</code> would hypothetically print <code>/home/user/MyDirectory/</code></li>
</ul>
<h3 id="heading-4">4</h3>
<h3 id="heading-nano"><code>nano</code></h3>
<ul>
<li>nano is a terminal file editor, nano does not always come pre-installed with every flavor of Linux. To create and start to edit a brand new text file: <code>nano main.py</code> will create a file called main.py and bring you right into the text editor.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671548315448/ulODAN_Kq.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-5">5</h3>
<h3 id="heading-cp"><code>cp</code></h3>
<ul>
<li><p>Copy a single file. <code>cp &lt;source&gt; &lt;destinationpath_and_filename&gt;</code></p>
</li>
<li><p>To copy <code>main.py</code> and place it in the same directory <code>cp main.py main_copy.py</code></p>
</li>
<li><p>To copy <code>main.py</code> to a new directory, first create the directory <code>mkdir NewFolder</code> and copy main into it <code>main.py NewFolder/main_copy.py</code></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How RuneScape Shaped Me]]></title><description><![CDATA[I have incredibly fond memories of RuneScape. One of my first experiences was playing via Miniclip in my 4th-grade classroom. A substitute teacher's son was visiting for the day and taught us young minds how to cook a raw chicken in Lumbridge.

I can...]]></description><link>https://datatribute.com/how-runescape-shaped-me</link><guid isPermaLink="true">https://datatribute.com/how-runescape-shaped-me</guid><category><![CDATA[runescape]]></category><category><![CDATA[IT]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Thu, 01 Dec 2022 19:19:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671553092573/quqJ4pgis.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have incredibly fond memories of RuneScape. One of my first experiences was playing via Miniclip in my 4th-grade classroom. A substitute teacher's son was visiting for the day and taught us young minds how to cook a raw chicken in Lumbridge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669057626419/8Dj8sU7_7.png" alt="image.png" /></p>
<p>I can't quite pin down the initial allure of RuneScape. In the poorest attempt to explain it.. a whole new universe was open to explore, with dangers, treasures and the greatest sense of community.</p>
<p>I didn't know it at the time but RuneScape (and World of Warcraft) was shaping my future. <strong>Gielinor was influencing how I now think about wealth accumulation, how I take actions in today's real world stock market, how I excel at my technology job and most importantly how I interact with others given a common goal.</strong></p>
<hr />
<h2 id="heading-bots-it-automation">Bots - IT Automation 💻</h2>
<p>RuneScape was my first exposure to IT automation, and more specifically, automation that I could clearly see at work, it was tangible. To get 99 Magic, the clearest, easiest path was to cast a High Alchemy spell repeatedly.. or about 198,000 times. 198k mouse clicks that took 3 seconds each... Point is, this was tedious.</p>
<p>I purchased a 'script' back in the day (I had no idea what a script meant as a 10-year-old). A High Alchemy bot that would move my mouse clicker for me as I launched this specific script client. It would randomly move sporadically to throw off the game's bot detections, then continue to cast the spell repeatedly without the need for me to be at my desk.</p>
<p>Now, these are nothing new and well-known nowadays but I was fascinated that these magical scripts could be created to automate the game. As a 10-year-old, I couldn't fathom how in the world someone would create such a thing. Now as a young professional, I'm learning Lua to script WoW add-ons.</p>
<p>In conclusion, bots or automation scripts opened my eyes to the fascinating world of IT automation. In my career, I've always been attracted to automating things and in fact, it's a large portion of my job description. I've created many automation scripts, small things like automatically moving my organization's Confluence pages to Sharepoint to developing full-scale data pipelines that update millions of records daily.</p>
<blockquote>
<h3 id="heading-it-automation-key-learnings">IT Automation - Key Learnings</h3>
<ul>
<li>Discovering a world where programmable scripts can save a tremendous amount of time and money</li>
</ul>
</blockquote>
<hr />
<h2 id="heading-law-runes-economics">Law Runes - Economics ⌛</h2>
<p>Brief context -- there's a concept of worlds in Runescape, which are just different servers that your character can log in to. You can easily hop between worlds and of course, all of your gear, money and stats are the same. However, certain worlds were unofficially known for certain events. I <em>believe</em> it was world 66, which was the Law Running world.</p>
<p>In the law running world, with no skills, gear or money, a fresh player could make money simply by running back and forth between a bank and a highly-skilled player crafting law runes, providing that player with a steady stream of raw materials. At a certain Runecrafting level, a player can make double the amount of law runes with the same amount of raw materials. This steady stream of raw materials saved the Runecrafters a TON of time and also made them money, given the ability to craft 2 law runes from 1 pure essence (the raw material).</p>
<p>In exchange for their service of running raw materials back and forth between the bank, law runners were given law runes.</p>
<p>Now... with that context out of the way. These law runners would accumulate law runes and they were looking to sell their amassed law runes in exchange for RuneScape gold.</p>
<p>I was not the first one to exploit this opportunity but I observed that I could buy law runes at a lower price at the bank that these law runners were hustling back and forth between. Then, I would teleport to an area where other players were training their magic and burning law runes to do so. These players training magic did not want to interrupt their gameplay to seek out more law runes. I'm sure you know where this is going -- I then sold those law runes to the players training magic at a slightly higher price than the price I paid at the law running bank.</p>
<blockquote>
<h3 id="heading-economics-key-learnings">Economics - Key Learnings</h3>
<ul>
<li><p>Buy Low, Sell High</p>
</li>
<li><p>People are lazy, or more fairly, people are willing to exchange money for time</p>
</li>
<li><p>Running law runes required no skill or experience. That's great. But given that, you also gained no skills or experience by being a runner; time is better spent building upon skills and experience, even if you take a monetary loss in the short term.</p>
</li>
</ul>
</blockquote>
<p><strong><em>Disclaimer:</em></strong> <em>For my RuneScape fans, this was before the grand exchange, a centralized system to post and purchase goods.</em></p>
<p><em>More Reading</em>: <a target="_blank" href="https://blogs.cornell.edu/info2040/2015/10/08/law-running-in-runescape/">Law Running - Cornell Edu</a></p>
<h2 id="heading-quests-requirements">Quests - Requirements ❗</h2>
<p>Quests in RuneScape required you to follow quest guides written by third parties. There are usually multiple steps involved, not always high in complexity but you did need to be organized and diligent to be efficient..and in doing so, avoid frustrating yourself.</p>
<p>Following quest guides forced me to sit down, read through the entire guide and THEN come up with a game plan. Gathering the requirements first, reading the guide once again, ensuring I have each step planned out and finally executing the quest.</p>
<p>I like to think this early exposure to reading digital documentation and executing the steps on a platform, albeit a video game, helped train my brain to the very analogous procedures I deal with as an IT professional.</p>
<p>For instance, given a business need to connect to an SFTP and download files on a continuous basis. I need to first gather the requirements.</p>
<ul>
<li><p>Does the SFTP require special credentials, via a VPN or other</p>
</li>
<li><p>When are the files being dropped?</p>
</li>
<li><p>Will the files be archived?</p>
</li>
<li><p>Are the files themselves encrypted with a privacy protocol?</p>
</li>
</ul>
<p>And lastly, I need to read the documentation on the SFTP python library paramiko to utilize the package's function to connect to the SFTP in a secure way.</p>
<blockquote>
<h3 id="heading-quests-key-learnings">Quests - Key Learnings</h3>
<ul>
<li><p>Requirement gathering is an advantageous skill in the world of software development. Learning how to do this all started with figuring out how many burnt chickens I needed and how much iron ore I need to mine and where to complete a Runescape quest!</p>
</li>
<li><p>Training myself to thoroughly read through technical documentation at an early age has helped me read through new materials and apply those learnings with speed and quality. Yes, it's arguable that the Recipe for Disaster quest is technical for a 10 year-old :)</p>
</li>
</ul>
</blockquote>
<hr />
<p>I won't elaborate on these last two but for those that have played other MMOs and probably relate with these bullets.</p>
<h3 id="heading-guilds-collaboration">Guilds - Collaboration</h3>
<h3 id="heading-the-wilderness-risk-vs-reward">The Wilderness - Risk vs. Reward</h3>
<hr />
<p>There is a lot of research pointing to the benefits of video games in our early years. At the very least, I think the argument that video games make teens violent is behind us. Regardless of your position on screen time for children and teens, Runescape, among other video games taught me valuable lessons, ones that I continue to reflect on in my professional and personal life.</p>
]]></content:encoded></item><item><title><![CDATA[Format a 64GB+ SD Card  |  Raspberry Pi]]></title><description><![CDATA[The Raspberry Pi OS (previously called Raspbian) will only flash to SD/USB cards formatted with FAT32 or FAT16 formats. These formats are common for flash storages devices less than 64GB.
However, If you have a 64GB, or larger SD/USB, you will need t...]]></description><link>https://datatribute.com/format-ssd-raspberry-pi</link><guid isPermaLink="true">https://datatribute.com/format-ssd-raspberry-pi</guid><category><![CDATA[Linux]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[ssd]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Mon, 24 Oct 2022 01:53:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666637989770/XmMPwIZav.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Raspberry Pi OS (previously called Raspbian) will only flash to SD/USB cards formatted with FAT32 or FAT16 formats. These formats are common for flash storages devices less than 64GB.</p>
<p>However, If you have a 64GB, or larger SD/USB, you will need to force format a FAT32 format type. Both Mac and Windows Disk Utilities usually do not give you the option to format a 64GB card to a FAT32 type. As such, you need to force the FAT32 type via the command line. </p>
<h2 id="heading-steps">Steps:</h2>
<ul>
<li>Open up a terminal and get a list of your mounted storage devices:</li>
</ul>
<pre><code>diskutil list
</code></pre><ul>
<li>Locate your SD/USB and take note of the <code>IDENTIFIER</code></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666576356768/I5beS0Bb9.png" alt="image.png" /></p>
<p><strong>NOTE</strong>: This next command will ERASE existing data on your device. 
<strong>Double check you've located the correct device
</strong></p>
<ul>
<li>Run command:<ul>
<li>change diskname to the name you want</li>
<li>change diskidentifier to your disk listed in the step above</li>
</ul>
</li>
</ul>
<pre><code>sudo diskutil eraseDisk FAT32 diskname MBRFormat /dev/diskidentifier
</code></pre><ul>
<li>If your SD/USB is mounted at /dev/disk2 and you want to rename the removable storage to 'RPI' your command will be: </li>
</ul>
<pre><code>sudo diskutil eraseDisk FAT32 RPI MBRFormat /dev/disk2
</code></pre><p>Now your storage device is ready to flash the Raspberry Pi OS image to the device. </p>
]]></content:encoded></item><item><title><![CDATA[Passing UTM Values to an Embedded Page]]></title><description><![CDATA[Scenario:
A parent page has an embedded page with a form or other interaction(s) that would be useful to capture the UTM values in conjunction with those interactions. 
The Issue:
A child (embedded) page does not, by default, communicate with the par...]]></description><link>https://datatribute.com/passing-utm-values-to-embedded-page</link><guid isPermaLink="true">https://datatribute.com/passing-utm-values-to-embedded-page</guid><category><![CDATA[utm]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[data]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 01 Jul 2022 18:15:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/tZc3vjPCk-Q/upload/v1659377671265/iEZKOfvYC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-scenario">Scenario:</h3>
<p>A parent page has an embedded page with a form or other interaction(s) that would be useful to capture the UTM values in conjunction with those interactions. </p>
<h3 id="heading-the-issue">The Issue:</h3>
<p>A child (embedded) page does not, by default, communicate with the parent page in regards to passing information back and forth. Therefore, UTM values are <strong>not</strong> passed from the parent page to the child page. </p>
<h4 id="heading-example">Example:</h4>
<p>For example, a user lands on the parent page -- <a target="_blank" href="https://datatribute.webflow.io/?utm_campaign=hashnode-utm">https://datatribute.webflow.io/?utm_campaign=hashnode-utm</a> -- and say there's an imaginary embedded page with a form submission on this parent page. The UTM campaign 'hashnode-utm' will not be passed to the form interactions. </p>
<p>This is especially problematic when the form is curated from a marketing platform like Pardot. Pardot can read UTM's directly and again, by default those UTMs from the parent child are not appropriately passed to the child page/form; withholding pertinent information  from Pardot's platform. </p>
<h3 id="heading-the-solution">The Solution:</h3>
<p>To pass UTMs to an embedded (child) page, we need to implement a very simple Custom HTML tag in GTM that fires on an 'All Pages' trigger:</p>
<pre><code><span class="hljs-operator">&lt;</span>script <span class="hljs-keyword">type</span><span class="hljs-operator">=</span><span class="hljs-string">"text/javascript"</span><span class="hljs-operator">&gt;</span>
<span class="hljs-keyword">var</span> iframe <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'replace_with_value_of_iframe_id'</span>);
iframe.src <span class="hljs-operator">=</span> iframe.src <span class="hljs-operator">+</span> window.location.search;
<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>script<span class="hljs-operator">&gt;</span>
</code></pre><ul>
<li><p>Get the element ID of your embedded page by inspecting the iframe on the parent page and search for something akin to <code>iframe id =</code> the value of this field is to be replaced in the above HTML script.</p>
</li>
<li><p>What this does is instead of passing the default URL of the parent page, it passes the entire URL from the parent page, including any extra parameters (UTMs) to the child page. </p>
</li>
<li><p>The iframe.src is essentially the child page's URL.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Python Flask - Dynamically Update Stock Ticker]]></title><description><![CDATA[My next iteration was to create a front-end application, hosted on the same rpi, that would allow anyone within my local network to change the stock ticker in realtime, simply by visiting the flask endpoint.
Flask Application
A simple front-end that ...]]></description><link>https://datatribute.com/python-flask-stock-ticker</link><guid isPermaLink="true">https://datatribute.com/python-flask-stock-ticker</guid><category><![CDATA[Python]]></category><category><![CDATA[Flask Framework]]></category><category><![CDATA[Business and Finance ]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 03 Jun 2022 15:17:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654268734159/OCZhlc6yR.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My next iteration was to create a front-end application, hosted on the same rpi, that would allow anyone within my local network to change the stock ticker in realtime, simply by visiting the flask endpoint.</p>
<h3 id="heading-flask-application">Flask Application</h3>
<p>A simple front-end that only took a few lines of code. The three user inputs correspond to the three tickers displayed on the pygame application. A user in my network can go to <code>192.xx.xx.xx:8082</code> within my local network to access this light-weight flask application.</p>
<h4 id="heading-front-end">Front-End</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654190597564/RtseZYaBw.png" alt="Screen Shot 2022-06-02 at 1.15.53 PM.png" /></p>
<h4 id="heading-flask-how-it-works">Flask - How It Works</h4>
<p>After a submission, the new tickers get written to a text file. The pygame application periodically checks if a modification has been made to the <code>ticker_files.txt</code> file. If there is a modification, the stock ticker application reads those new tickers and pulls from the yfinance module. </p>
<p>A few normalization and error checking steps:</p>
<ul>
<li>First check if the ticker is valid by hitting the yfinance module</li>
<li>Stripping unnecessary quotes </li>
<li>Writing to the text file </li>
</ul>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/python3</span>

<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, render_template, url_for, request
<span class="hljs-keyword">import</span> yfinance <span class="hljs-keyword">as</span> yf

app = Flask(__name__)

<span class="hljs-meta">@app.route('/', methods =["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span>():</span>
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        tk1 = request.form.get(<span class="hljs-string">"tk1"</span>)
        tk2 = request.form.get(<span class="hljs-string">"tk2"</span>)
        tk3 = request.form.get(<span class="hljs-string">"tk3"</span>)
        ticker_list = [tk1,tk2,tk3]
        ticker_list_names = []
        ticker_string = <span class="hljs-string">""</span>
        <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> ticker_list:
            t = t.upper()
            ticker_string += t + <span class="hljs-string">","</span>
            t_object = yf.Ticker(t)
            <span class="hljs-keyword">if</span> t_object.info[<span class="hljs-string">"regularMarketPrice"</span>] == <span class="hljs-literal">None</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-string">"You have submitted an incorrect symbol: "</span>+ t + <span class="hljs-string">", go back and resubmit"</span>

            <span class="hljs-keyword">else</span>:
                ticker_list_names.append(t_object.info[<span class="hljs-string">"shortName"</span>].split(<span class="hljs-string">" "</span>)[<span class="hljs-number">0</span>])

        print(ticker_list)
        print(ticker_list_names)
        ticker_string +=  ticker_list_names[<span class="hljs-number">0</span>].replace(<span class="hljs-string">","</span>,<span class="hljs-string">""</span>) + <span class="hljs-string">","</span>
        ticker_string +=  ticker_list_names[<span class="hljs-number">1</span>].replace(<span class="hljs-string">","</span>,<span class="hljs-string">""</span>) + <span class="hljs-string">","</span>
        ticker_string +=  ticker_list_names[<span class="hljs-number">2</span>].replace(<span class="hljs-string">","</span>,<span class="hljs-string">""</span>)

        text_file = open(<span class="hljs-string">"/home/pi/Desktop/ticker_files.txt"</span>,<span class="hljs-string">"w+"</span>)
        text_file.writelines(ticker_string)

        <span class="hljs-keyword">return</span>  <span class="hljs-string">"You've submitted "</span> + tk1 + <span class="hljs-string">" "</span> + tk2 + <span class="hljs-string">" "</span> +tk3


    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"home.html"</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">flask_run</span>():</span>
    app.run(host=<span class="hljs-string">'0.0.0.0'</span>,port=<span class="hljs-string">'8082'</span>)

flask_run()
</code></pre>
<p>This function is called every other second to check if there was a modification (by timestamp) to the text file. If there was a change, indicating that a user has submitted new tickers, the ticker symbols are returned and are used going forward to call the prices and % changes. </p>
<pre><code class="lang-python">stamp_old = <span class="hljs-number">0</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_file_change</span>():</span>
    <span class="hljs-keyword">global</span> stamp_old
    stamp_new = os.stat(<span class="hljs-string">"/home/pi/Desktop/ticker_files.txt"</span>).st_mtime
    <span class="hljs-keyword">if</span> stamp_new != stamp_old:
        <span class="hljs-comment">#file changed</span>
        stamp_old = stamp_new
        <span class="hljs-keyword">with</span> open(<span class="hljs-string">"/home/pi/Desktop/ticker_files.txt"</span>,<span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> f:
            lines = f.readlines()
            <span class="hljs-keyword">return</span> lines
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[5 Things You Didn't Know Existed in Google's Looker Studio]]></title><description><![CDATA[1. Version Control
Version control in the Business Intelligence world has never really been a thing, at least in my experience. In 2021, Google came out with a, somewhat rudimentary, version control feature in Looker. Although basic, it helps a ton w...]]></description><link>https://datatribute.com/5-things-you-didnt-know-existed-in-googles-looker-studio</link><guid isPermaLink="true">https://datatribute.com/5-things-you-didnt-know-existed-in-googles-looker-studio</guid><category><![CDATA[#data visualisation]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Wed, 01 Jun 2022 01:03:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/vVHXeu0YNbk/upload/v1654045354205/Z6IofpSfB.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-1-version-control">1. Version Control</h3>
<p>Version control in the Business Intelligence world has never really been a thing, at least in my experience. In 2021, Google came out with a, somewhat rudimentary, version control feature in Looker. Although basic, it helps a ton when working with larger reports that need constant, lengthy updates.</p>
<p>You can enable 'publishing' in Looker and switch between Draft vs. Published versions; allowing you to work on your report without the end user seeing it live.</p>
<p>To enable:</p>
<ul>
<li><p>Edit Mode</p>
</li>
<li><p>File -&gt; Publishing Settings</p>
</li>
</ul>
<hr />
<p>Once enabled, this will appear at the top of your report:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654044787468/WdQD8KJKw.PNG" alt="draft.PNG" /></p>
<p><a target="_blank" href="https://support.google.com/datastudio/answer/10382047?hl=en#zippy=%2Cin-this-article">Google Support Docs:</a></p>
<h3 id="heading-2-parameters">2. Parameters</h3>
<p>Parameters in Looker are similar to parameters in Tableau. Parameters allow dynamic input by the user, allowing real time computations. You can also pass parameter inputs back to custom SQL. Allowing a query like:</p>
<pre><code class="lang-python">SELECT [parameter_to_select]
FROM myBigQuerytable
LIMIT <span class="hljs-number">1</span>;
</code></pre>
<p>Another example of the dynamic power of parameters is using Age as an input from the end user. Given the input, you can then do custom calculations, like displaying that person's birth year.</p>
<h4 id="heading-parameter-setup">Parameter Setup:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654045670840/DLqq8bfE4.PNG" alt="param.PNG" /></p>
<p>Birth Year calculation: <code>DATETIME_SUB(today(),INTERVAl Age YEAR)</code></p>
<h4 id="heading-user-input-and-birth-year-calculated">User Input and Birth Year Calculated:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654046067714/R5rjGuEzT.PNG" alt="paramage.PNG" /></p>
<p><a target="_blank" href="https://support.google.com/datastudio/answer/9002005?hl=en#zippy=%2Cin-this-article">Google Support Docs</a></p>
<h3 id="heading-3-page-andamp-report-level-filters">3. Page &amp; Report Level Filters</h3>
<p>Ever found yourself endlessly applying filters to each scorecard or table on a page? Well you can create a page OR report level filter and by default, it's applied universally to all report or page objects.</p>
<h4 id="heading-page-level">Page Level:</h4>
<p>Page -&gt; Current Page Settings -&gt; Add a Filter</p>
<h4 id="heading-report-level">Report Level:</h4>
<p>File -&gt; Report Settings -&gt; Add a Filter</p>
<hr />
<p>If there are a few objects you don't want the higher level filter applied to, you can choose not to inherit that filter under that object's data properties.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654044806896/7vPKKD6MM.PNG" alt="page.PNG" /></p>
<h3 id="heading-4-drill-down-metrics">4. Drill Down Metrics</h3>
<p>Visualizing hierarchically related dimensions can be tricky. GDS offers a drill down option for related dimensions. Take an Asset Type dimension and an Assest SubType. When properly set up, the end user can drill deeper into hierarchical dimensions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654044647868/lMcv0l4id.PNG" alt="drilldown.PNG" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654044454513/0Vdty21PH.gif" alt="Animation.gif" /></p>
<h3 id="heading-5-align-page-objects">5. Align Page Objects</h3>
<p>Right click while selecting 2 or more objects on the page and align them either vertically or horizontally. For instance, this option in the screenshot below will snap the two tables vertically in line with each other (based on the middle marker of each visual)</p>
<p>This makes formatting scorecards, tables and visualizations incredibly easy. No more dragging a millimeter here, a millimeter there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654045277507/Y1sDucgI9.gif" alt="align.gif" /></p>
]]></content:encoded></item><item><title><![CDATA[UTM Tracking Basics]]></title><description><![CDATA[The primary purpose of this document is to provide insight into what UTMs are, how these (UTM) parameters function, how to build them and how to get deeper insights into reporting with the combination of UTMs and custom (Google Tag Manager) event tra...]]></description><link>https://datatribute.com/utm-tracking-basics</link><guid isPermaLink="true">https://datatribute.com/utm-tracking-basics</guid><category><![CDATA[utm]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[tracking]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 27 May 2022 18:35:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/CyX3ZAti5DA/upload/v1659378103019/Q5r7sbPJR.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The primary purpose of this document is to provide insight into what UTMs are, how these (UTM) parameters function, how to build them and how to get deeper insights into reporting with the combination of UTMs and custom (Google Tag Manager) event tracking. </p>
<h2 id="heading-introduction-utm-urchin-tracking-module">Introduction: UTM (Urchin Tracking Module)</h2>
<h3 id="heading-what">What?</h3>
<p>A tracking structure that functions by appending parameters (UTM ‘codes’) to the end of a URL that Google Analytics can read automatically</p>
<h3 id="heading-why">Why?</h3>
<p>UTM fields like utm_source=email_newsletter can give deeper insight into reporting and actions taken by certain cohorts and or email marketing campaigns as a whole</p>
<h3 id="heading-how">How?</h3>
<p>UTMs are appended manually and automatically (by certain email marketing platforms). These parameters can be read by Google Analytics and thus, pivotable by other GA metrics. For instance, we could see other actions, such as downloads taken by users who came through the hypothetical UTM source of: utm_source=email_newsletter</p>
<h3 id="heading-summary">Summary:</h3>
<p>When UTMs are deployed and built with consistency; they become an added layer of insight. UTM parameters are fields that are appended at the end of a URL. These fields can be read automatically by Google Analytics. UTMs are tied to the user’s browser cookie. </p>
<p>Therefore, the information in the UTM is carried with the user throughout their current and future web session (barring cookie expiration, cache wipe and new UTMs overriding existing ones). As such, this allows analyses to be done on certain cohorts, audiences, and platforms. Ie. business questions such as “are users more engaged on the website that come from organic search or that come from our curated newsletter?” </p>
<h2 id="heading-utm-structure">UTM Structure:</h2>
<h3 id="heading-example-utm">Example UTM:</h3>
<pre><code><span class="hljs-attribute">https</span>:<span class="hljs-comment">//datatribute.webflow.io/?utm_source=newsletter&amp;utm_medium=email&amp;utm_campaign=product_sale</span>
</code></pre><p>There are five UTM fields that can be utilized. However for most web reporting standards, the first three are the ones that will and should be used the most. </p>
<ul>
<li>Medium</li>
<li>Source</li>
<li>Campaign</li>
<li>Content</li>
<li>Term</li>
</ul>
<hr />
<h3 id="heading-medium-required-utmmedium">Medium (required): utm_medium</h3>
<p>Used to identify the channel origination of the link. What channel did the user receive this link from?</p>
<h4 id="heading-examples">examples:</h4>
<ul>
<li>google</li>
<li>email</li>
<li>social</li>
</ul>
<h3 id="heading-source-required-utmsource">Source (required): utm_source</h3>
<p>Subset of Medium at a more granular level. Used to identify the specific source of content, what site or platform did the user receive this link from? </p>
<h4 id="heading-examples">examples:</h4>
<ul>
<li>facebook</li>
<li>twitter</li>
<li>mailchimp</li>
<li>salesforce</li>
</ul>
<h3 id="heading-campaign-required-utmcampaign">Campaign (required): (utm_campaign)</h3>
<p>Used to identify a specific initiative.</p>
<h4 id="heading-examples">examples:</h4>
<ul>
<li>spring_sale</li>
<li>product_sale</li>
<li>june_newsletter</li>
</ul>
<h3 id="heading-term-utmterm">Term: (utm_term)</h3>
<p>Used to identify keywords in an ad (can be used for versioning of newsletter) </p>
<h4 id="heading-examples">examples:</h4>
<ul>
<li>shoes</li>
<li>womens shoes</li>
<li>red shoes</li>
<li>sport shoes</li>
</ul>
<p>Not commonly used outside of cost per click (cpc) advertising</p>
<h4 id="heading-content-utmcontent">Content: (utm_content)</h4>
<p>Used to differentiate type of ads (can also be used for versioning of newsletter) </p>
<h4 id="heading-examples">examples:</h4>
<ul>
<li>extended_text</li>
<li>display</li>
<li>auto_ad</li>
</ul>
<p>Not commonly used outside of cost per click (cpc) advertising </p>
<h4 id="heading-important-note">Important Note:</h4>
<p>There are no hard and fast rules for these parameters, except for medium and source. Medium and source be consistently used and enforced by the common ruleset. However, the other parameters can be independently defined on how to use them based on specific reporting and segmentation needs.  </p>
<h2 id="heading-building-and-record-keeping">Building and Record Keeping</h2>
<p>UTMs are unfortunately very manual to build. Unless links and content are utilizing a software or platform like Mailchimp or Hootsuite where UTMs can be automatically appended to all links. </p>
<p>I recommend keeping track of all UTMs that are built in a spreadsheet.  </p>
<h3 id="heading-implementing">Implementing</h3>
<p>After the URLs are built, the links should be copied and pasted in the platform where you are sending the links out from. For instance, UTM links can be copied into tweets, newsletter content, facebook posts etc.  </p>
<hr />
<h2 id="heading-hypothetical-utm-use-case">Hypothetical UTM Use-Case</h2>
<p>I’m going to send a curated email to my top 100 customers from my personal gmail account. There’s a special offer on my website that I’m going to link my clients to, directly within my email. I want to know how well (or poorly) the personal email drove sales to this special offer as opposed to another messaging communication. </p>
<h3 id="heading-create-the-utm">Create the UTM</h3>
<p><code>https://https://datatribute.webflow.io/?utm_source=newsletter&amp;utm_medium=email&amp;utm_campaign=top_client_personal_email</code></p>
<h3 id="heading-paste-into-my-email">Paste into my email</h3>
<p>Using a hyperlink to ‘hide’ the UTMs: <a target="_blank" href="https://https://datatribute.webflow.io/?utm_source=newsletter&amp;utm_medium=email&amp;utm_campaign=top_client_personal_email">“Check out this special offer!”</a></p>
<h3 id="heading-view-insights-in-google-analytics">View insights in Google Analytics</h3>
<p>In GA, I will be able to see, by filtering by this campaign, if this specific messaging communication drove any sales.</p>
<hr />
<h3 id="heading-utms-andamp-custom-event-tracking">UTMs &amp; Custom Event Tracking</h3>
<p>In combination with custom event tracking the power in UTMs is gaining a deeper level of insight into user behavior. To unlock this insight, custom events in Google Tag Manager can be set up to track specific behavior for specific audiences or initiatives. For example, did the June newsletter drive more downloads(event tracking) than the July newsletter, if so why and how can we improve the newsletter content to drive incremental growth? </p>
<p>Filtering by UTMs in Google Analytics is simple and easy. After UTMs are appended and a link is clicked - GA handles the rest (funneling, sorting and tying with other data). Any other action the user performs on the site, the UTM is carried with them. </p>
]]></content:encoded></item><item><title><![CDATA[How to Prepare for a Technical Data Engineer Interview]]></title><description><![CDATA[I recently went through a number of interviews for Data Engineering roles. Most of them at the senior, individual contributor level. This is an aggregation of some of the questions I was asked throughout the technical rounds. 
The core focus was of c...]]></description><link>https://datatribute.com/how-to-prepare-for-a-technical-data-engineer-interview</link><guid isPermaLink="true">https://datatribute.com/how-to-prepare-for-a-technical-data-engineer-interview</guid><category><![CDATA[data]]></category><category><![CDATA[interview]]></category><category><![CDATA[SQL]]></category><category><![CDATA[Python]]></category><category><![CDATA[Technical interview]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Wed, 09 Mar 2022 15:40:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/80VTQEkRh1c/upload/v1646840321017/tPHLStzNW.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently went through a number of interviews for Data Engineering roles. Most of them at the senior, individual contributor level. This is an aggregation of some of the questions I was asked throughout the technical rounds. </p>
<p>The core focus was of course SQL and Python with some data infrastructure and system design questions. Below is a breakdown of the type of technical questions asked as well sample questions and prompts. </p>
<ul>
<li><strong>SQL:</strong> 45% </li>
<li><strong>Python:</strong> 35%   </li>
<li><strong>Data Systems Design:</strong> 20%</li>
</ul>
<hr />
<h3 id="heading-sql">SQL</h3>
<h4 id="heading-verbal">Verbal:</h4>
<ul>
<li>Explain the difference between a left join and an inner join</li>
<li>What's the difference between Union vs. Union All?</li>
<li>Explain what indexing is and it's purpose in a RDBMS</li>
<li>When analyzing a slow query, what steps would you take to diagnose the issue? </li>
</ul>
<h4 id="heading-written">Written:</h4>
<p>Given an order and customer table, answer the following questions:</p>
<ul>
<li>Take both order and customer tables and produce the <code>order_amount</code> for all <code>customer_ids</code></li>
<li>Take the order table and sum the <code>order_amount</code> for each month and display by
<code>order_type</code>. Each <code>order_type</code> should be in its own column.</li>
<li>Take the order table and generate an <code>average_order_type_amount</code>, by <code>order_type</code> for each row. </li>
</ul>
<p><strong>Order Table:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>customer_id</td><td>order_type</td><td>date_of_order</td><td>order_amount</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>A</td><td>01/02/2021</td><td>250</td></tr>
<tr>
<td>2</td><td>B</td><td>02/12/2021</td><td>500</td></tr>
<tr>
<td>3</td><td>C</td><td>04/08/2021</td><td>600</td></tr>
<tr>
<td>4</td><td>B</td><td>03/05/2021</td><td>150</td></tr>
<tr>
<td>5</td><td>A</td><td>01/27/2021</td><td>230</td></tr>
<tr>
<td>6</td><td>A</td><td>03/14/2021</td><td>250</td></tr>
<tr>
<td>1</td><td>B</td><td>04/13/2021</td><td>405</td></tr>
</tbody>
</table>
</div><p><strong>Customer Table:</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>customer_id</td><td>name</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>Jasper</td></tr>
<tr>
<td>2</td><td>Will</td></tr>
<tr>
<td>3</td><td>Ben</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-python">Python</h3>
<h4 id="heading-verbal">Verbal:</h4>
<ul>
<li>What's the difference between a list and a tuple?</li>
<li>Explain what a dictionary is</li>
<li>In pseudocode, explain a how to encode a string input to <code>$</code> if the character repeats or to <code>^</code> if the character does not repeat. <ul>
<li>For instance, <code>temperature</code> would encode to <code>$$^^$$^$^$$</code></li>
<li><code>t</code> repeats twice and thus encoded to  <code>$</code></li>
</ul>
</li>
</ul>
<ul>
<li>What is the output of this function? <pre><code>mydata = {<span class="hljs-string">'foo'</span>: <span class="hljs-string">'foo'</span>, <span class="hljs-string">'bar'</span>: <span class="hljs-string">'bar'</span>}
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span>(<span class="hljs-params">data</span>):</span>
 local_data = data
 local_data[<span class="hljs-string">'foo'</span>] = <span class="hljs-string">'baz'</span>
 transform(mydata)
 print(mydata[<span class="hljs-string">'foo'</span>])
</code></pre></li>
</ul>
<h4 id="heading-written">Written:</h4>
<ul>
<li>Given a string as an input, write a function to replace all <code>#</code> hashtags  with a <code>-</code>  and replace all <code>&amp;</code> ampersands with a <code>+</code><ul>
<li>For example, <code>###&amp;##</code> should return <code>---+--</code></li>
</ul>
</li>
</ul>
<ul>
<li><p>Find the longest consecutive recurrence of an element in a list. Given this list:</p>
<pre><code>[A ,C ,D, D, D, B, C, C, A, F, A, D, A, A,]
</code></pre></li>
<li><p>Print dictionary in alphabetic order by first name. Expected output <code>Anthony is in IT, Ben is in HR...</code></p>
<pre><code class="lang-`">mydictionary = {'Jess': 'IT', 'Will': 'Sales', 'Anthony': 'IT', 'Ben':
'HR' }
</code></pre>
</li>
</ul>
<hr />
<h3 id="heading-data-systems-design">Data Systems Design</h3>
<h4 id="heading-prompt-example">Prompt Example:</h4>
<p>Be prepared to talk about the architectural design of data infrastructure you have worked on, preferably acted as the lead engineer. Prepare architecture diagrams in advance, or you can draw one live as you explain the design. </p>
<ul>
<li>Explain the infrastructure and the principles that led to this architecture</li>
<li>Explain the reasoning behind different design decisions and alternative solutions</li>
<li>Explain further extensions of the project for future requests </li>
<li>What was the overall impact the project had? </li>
</ul>
<h4 id="heading-questions">Questions:</h4>
<ul>
<li>How would you design a scalable pipeline to ingest flat files? </li>
<li>How would you design an API client to interact with a microservice? </li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Top Free Services For a Data Junkie]]></title><description><![CDATA[I've stumbled upon numerous services over the years that kindly offer their products for free. If you are a hobbyist or just starting to learn, the free tier of these products listed below are plenty for you to get started. 
Virtual Machines:
Linode
...]]></description><link>https://datatribute.com/top-free-services-for-a-data-junkie</link><guid isPermaLink="true">https://datatribute.com/top-free-services-for-a-data-junkie</guid><category><![CDATA[Databases]]></category><category><![CDATA[data]]></category><category><![CDATA[free]]></category><category><![CDATA[NoSQL]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Sat, 22 Jan 2022 19:41:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/hbwdmqcmP6k/upload/v1642868215231/TEQC4dHXm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've stumbled upon numerous services over the years that kindly offer their products for free. If you are a hobbyist or just starting to learn, the free tier of these products listed below are plenty for you to get started. </p>
<h2 id="heading-virtual-machines">Virtual Machines:</h2>
<h3 id="heading-linode">Linode</h3>
<p>Upon signup, you will receive $100 in credits - you do however have to enter a credit card to have on file. If you're just tinkering around, you should never come close to the limit and should never be billed. </p>
<p>$100 is enough for you spin up a 1 CPU, 25GB Storage and 1GB of RAM Ubuntu VM and have it running 24/7 for 20 months! </p>
<p>Safe to say I'll never run out of credits, as I usually tinker around, then tear it down immediately. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642864436778/qNjp__BV_.png" alt="image.png" /></p>
<p>https://www.linode.com/</p>
<hr />
<h2 id="heading-lowithno-code-web-builders">Low/No Code Web Builders:</h2>
<p>As someone who's not an expert on front-end languages, I have a ton of fun learning and playing around with these page builders. </p>
<h3 id="heading-webflow">Webflow</h3>
<p>The Free Starter plan offers you plenty of features to get started, if you'd like to add your own domain name, embed more custom features, add custom HTML and rid the Webflow white label, you'll have to upgrade. </p>
<p>However, the free plan allowed me to build a few pages, perfect for my needs. I built a portfolio site on Webflow that houses my data visualization gallery and client projects. </p>
<p>The Starter plan only allows two static pages which is kind of a bummer however you can use a little trick to create more. I've added a few CMS Collection pages and use those to add more static pages. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642865046015/d7wljBtmu.png" alt="image.png" /></p>
<p>The functionality of this web builder is more detailed and feature rich than a Wix or Squarepsace, something more applicable to superusers who are looking to customize their sites a little more. If you're looking for super easy drag and drop features I'd recommend Wix or Squarespace but Webflow is still super easy, there is just a slight learning curve.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642865088617/onQi8_9aJ.png" alt="image.png" /></p>
<p>https://webflow.com/</p>
<h3 id="heading-hashnode">Hashnode</h3>
<p>If you're reading this from Hashnode, chances are you have a Hashnode account - I am really enjoying writing articles on this site, it's super user friendly and allows the writer to just go, no fussing around with blocks, margins, picture scaling etc. </p>
<p>If you're reading this elsewhere or on Hashnode and you don't have an account yet. Sign up and get writing! </p>
<h3 id="heading-nicepage">Nicepage</h3>
<p>I've created Python Flask apps that look like they're from the 90s. Nicepage has allowed me to easily design a front end, export it and plop it into my Flask app. </p>
<p>I couldn't tell you the limitations of the free tier of Nicepage because I haven't run into any blockers. The difference for me with Nicepage and Webflow is that I can import and export with Nicepage. Once I export I can delete the project in Nicepage and save it locally, in case I ever want to make any changes. </p>
<p>You can of course also publish through Nicepage but I've used it solely to add a little jazz to my Python applications. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642866433869/id8GRNhYY.png" alt="image.png" /></p>
<p>https://nicepage.com/</p>
<hr />
<h2 id="heading-databases">Databases:</h2>
<h3 id="heading-elephant-sql">Elephant SQL</h3>
<p>Elephant SQL is one of the best services I've come across for messing around with a small db server. It's PostgreSQL and their free version gives you 20MB of data with 5 concurrent connections. </p>
<p>Now, 20MB is not a ton, but if you're just messing around with hooking a db server into your application or want to store personal records, it's perfect. I've personally been building a script on my microcontroller that's reading my houses's temperature and humidity, to batch insert data to my ElephantSQL instance every few hours. 20MB is plenty for this use case. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642867208144/k8cDiw7tI.png" alt="image.png" /></p>
<p>https://www.elephantsql.com/</p>
<h3 id="heading-mongodb">MongoDB</h3>
<p>MongoDB has a shared cluster that is free FOREVER with 512MB of storage. It's not going to be the fastest thing but it's a great service that can give you a foray into NoSQL, document type storage systems. </p>
<p>Just remember if you spin up an instance - to create a connection to it every so often. They will discontinue your database if it's inactive for a certain period.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642867618189/HRRRlDmIrh.png" alt="image.png" /></p>
<p>https://www.mongodb.com/</p>
<hr />
<h2 id="heading-computing">Computing:</h2>
<h3 id="heading-aws-lambda">AWS Lambda</h3>
<p>I run several tiny Python scripts on AWS Lambda, which is a serverless computing service offered by AWS. I have a home server but I don't run it 24/7. Lambda allows me to run small automation tasks that cost me $0. This isn't just through the free tier, if your scripts use little memory and don't take long to run, they can be essentially free.. or a fraction of a penny. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642879592721/2T7pAxUs2.png" alt="image.png" /></p>
<p>https://dashbird.io/lambda-cost-calculator/</p>
<p>Check out my other article to learn more about getting a Python function to run on Lambda: </p>
<ul>
<li><a target="_blank" href="https://datatribute.com/automate-the-luxury-stuff-with-python#heading-serverless-computing">Serverless Computing</a></li>
</ul>
<h3 id="heading-heroku">Heroku</h3>
<p>Just created a Django app but don't know where to host and publish it? Heroku has great guides for many supported languages. You can't add a custom domain but to get your app published and usable on the web within an hour, the Heroku free tier is a great option to host a small hobby or test application. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642867847783/HYZLiyZ8f.png" alt="image.png" /></p>
<p>https://devcenter.heroku.com/articles/deploying-python</p>
<hr />
<h2 id="heading-productivity">Productivity</h2>
<h3 id="heading-confluence">Confluence</h3>
<p>I was getting tired of Google Docs, of course there's a hierarchical structure to it with folders but I wanted something easier to manage, edit, add content and organize. </p>
<p>Confluence allowed me to organize my life a bit better than Google Docs. I can create hierarchies through child/parent pages, take notes, drop pictures in without formatting issues, drop in easy code snippets or create a table - it's just 10x easier to use than Google Drive suite - at least for personal productivity and digital life organization. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642878879852/u6v_8IaVG.png" alt="image.png" /></p>
<p>I'll still use GDrive to host large file dumps but in terms of actual content, my notes, ideas and lists all live on my private Confluence Space. </p>
<h3 id="heading-notion">Notion</h3>
<p>I jumped on the Notion bandwagon.. I couldn't get myself to fully transition from Confluence to Notion for personal productivity. However, I did find Notion easier for daily task tracking and pages that I may want to publish to family members or friends. It's great on mobile devices. </p>
<p>I use notion for daily journaling and to organize my book list. The embedded page features is nice to have a 'storefront' of my books, all organized and if I click into one, I've taken notes and given it comments after I've finished reading. I couldn't quite create this in Confluence with the format I wanted. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642878992221/4t1iC2KSX.png" alt="image.png" /></p>
<hr />
]]></content:encoded></item><item><title><![CDATA[DHT11 Temperature & Humidity Sensor - Microcontroller]]></title><description><![CDATA[I had a tough time getting the ESP8266 Microcontroller to read from a DHT11 sensor properly, mainly due to small errors in my code and process.
Learnings Along the Way:

setting the I/O pin number to '8' was not working and giving a nan reading. Chan...]]></description><link>https://datatribute.com/dht11-temperature-and-humidity-sensor-microcontroller</link><guid isPermaLink="true">https://datatribute.com/dht11-temperature-and-humidity-sensor-microcontroller</guid><category><![CDATA[iot]]></category><category><![CDATA[arduino]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Sat, 22 Jan 2022 19:09:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642878454147/W1IGGL7IW.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I had a tough time getting the ESP8266 Microcontroller to read from a DHT11 sensor properly, mainly due to small errors in my code and process.</p>
<h3 id="heading-learnings-along-the-way">Learnings Along the Way:</h3>
<ul>
<li>setting the I/O pin number to '8' was not working and giving a nan reading. Changing to 'D8' however worked. </li>
<li>I am not able to upload a script while the input pin (D8) is connected to the board. Arduino IDE throws an error that it either can't find the port or it's in use. <ul>
<li>First I had to disconnect the D8 pin from the board, leaving the ground and power pins in. <ul>
<li>Then I'm able to upload the script to the microcontroller</li>
<li>After uploading, I reconnect the input pin to the microcontroller. I'm able to do this while the board has usb power</li>
</ul>
</li>
</ul>
</li>
<li>passing true in <code>dht.readTemperature(true);</code> changes the output to Fahrenheit from Celsius </li>
</ul>
<h3 id="heading-working-code">Working code:</h3>
<pre><code class="lang-C"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"DHT.h"</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> DHTTYPE DHT11 <span class="hljs-comment">//change to DHT22 if needed</span></span>

<span class="hljs-function">DHT <span class="hljs-title">dht</span><span class="hljs-params">(D8,DHTTYPE)</span></span>; 
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span>
</span>{

  dht.begin();
  Serial.begin(<span class="hljs-number">115200</span>);
  Serial.println(<span class="hljs-string">"setup"</span>);
  delay(<span class="hljs-number">1000</span>);<span class="hljs-comment">//Wait before accessing Sensor</span>

}<span class="hljs-comment">//end "setup()"</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">loop</span><span class="hljs-params">()</span> </span>{

  <span class="hljs-keyword">float</span> h =dht.readHumidity();
  <span class="hljs-keyword">float</span> t =dht.readTemperature(<span class="hljs-literal">true</span>);

  Serial.print(<span class="hljs-string">"Current Humidity = "</span>);
  Serial.print(h);
  Serial.print(<span class="hljs-string">"% "</span>);
  Serial.print(<span class="hljs-string">"Temperature = "</span>);
  Serial.print(t);
  Serial.println(<span class="hljs-string">"DHT11 Humidity &amp; temperature Sensor\n\n"</span>);
  delay(<span class="hljs-number">800</span>);
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Analyzing Reddit Comments with Python - FIRE Subreddit]]></title><description><![CDATA[What is the average, median and max dollar amount that's referenced on a popular finance subreddit? This is the question I sought after.

I browse a few finance related subreddits and I was always curious to see how often a certain dollar amount is r...]]></description><link>https://datatribute.com/python-scraping-analyzing-reddit-comments</link><guid isPermaLink="true">https://datatribute.com/python-scraping-analyzing-reddit-comments</guid><category><![CDATA[Python]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 14 Jan 2022 19:49:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642174492289/2nxjI3iEn.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What is the average, median and max dollar amount that's referenced on a popular finance subreddit? This is the question I sought after.</p>
<hr />
<p>I browse a few finance related subreddits and I was always curious to see how often a certain dollar amount is referenced in the comments. Instead of picking a few arbitrary numbers I decided to gather a few baseline statistics, like the average, median and max dollar amounts that are referenced in the comment section of posts.</p>
<p>‍</p>
<p>For this scenario I pulled from the FIRE, /r/financialindependence subreddit using reddit's API. This subreddit has a daily post titled "Daily Discussion". I figured this would be fairly easy to pull and aggregate from, given the consistency of the daily post titles. In the future it would be interesting to see how these numbers compare with another finance subreddit like /r/personalfinance.</p>
<h3 id="heading-authenticating">Authenticating</h3>
<p>Reddit's API is super straightforward, I created a separate account solely for Reddit projects to keep things clean from my main account. Refer to their documentation to get your credentials.</p>
<p>These are the parameters you'll need to fill in:</p>
<pre><code class="lang-python"><span class="hljs-comment">#private creds here</span>
reddit = praw.Reddit(
    client_id=,
    client_secret=,
    user_agent=,
    username=,
    password=,
)
</code></pre>
<h3 id="heading-subreddit-search">Subreddit Search</h3>
<p>In my case, the daily FIRE posts have the exact same title. As such, I didn't have to do any tweaking to capture only daily discussion posts  </p>
<ol>
<li>Choose subreddit</li>
<li>Search posts within that subreddit. This will return an object of submission IDs matching the search</li>
<li>Convert into a list of string values </li>
</ol>
<pre><code class="lang-python">subreddit = reddit.subreddit(<span class="hljs-string">"financialindependence"</span>)

submissions = subreddit.search(
    <span class="hljs-string">"Daily FI discussion thread"</span>, limit=<span class="hljs-number">100</span>
)  <span class="hljs-comment"># returns submission ids matching this, it's an object(iterator)</span>

submission_list = list(submissions)  <span class="hljs-comment"># list of string values for each daily id</span>
</code></pre>
<h3 id="heading-creating-a-submission-instance">Creating a Submission Instance</h3>
<p>The prior step just gathered the IDs of each submission, now we need to instantiate each submission to gather the metadata associated with each submission.</p>
<pre><code class="lang-python">submission_classes = []
<span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> submission_list:
    submission_classes.append(reddit.submission(
        x))
</code></pre>
<h3 id="heading-grabbing-comments">Grabbing Comments</h3>
<p>Now we need to loop through each submission and grab the comment body in each submission.</p>
<ol>
<li>Loop through each submission</li>
<li>Loop through each comment in the comment body of each submission
‍</li>
</ol>
<p>This line below <em>comments.replace.more </em>will dictate how far down the comment forest the API needs to crawl. With a limit set to None, this took hours to run. I chose to set the limit at 0 which should just gather the top level comments.</p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> submissionx <span class="hljs-keyword">in</span> submission_classes:
    submissionx.comments.replace_more(limit=<span class="hljs-literal">None</span>)
    sub_title = submissionx.title
</code></pre>
<p>Given my goal, I only need comments that contain a USD currency symbol to extract my needed data points. To do that I used a regular expression matching '$' anywhere in the comment: </p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> comment <span class="hljs-keyword">in</span> submissionx.comments.list():
        comment_list.append(comment.body.encode(
            <span class="hljs-string">"utf-8"</span>))
</code></pre>
<h3 id="heading-normalizing-comments">Normalizing Comments</h3>
<p>Of course there are many ways someone can write money. Such as:</p>
<ul>
<li>$20k</li>
<li>$20k-50k</li>
<li>$1M</li>
<li>$1,353</li>
<li>$50k/year</li>
</ul>
<ol>
<li>To normalize these comments I first replaced each common shorthand with the respective number of 0s using a dictionary: </li>
</ol>
<pre><code class="lang-python">replace_dict = {
    <span class="hljs-string">'k'</span>: <span class="hljs-string">'000'</span>,
    <span class="hljs-string">'K'</span>: <span class="hljs-string">'000'</span>,
    <span class="hljs-string">'M'</span>: <span class="hljs-string">'000000'</span>,
    <span class="hljs-string">'m'</span>: <span class="hljs-string">'000000'</span>,
    <span class="hljs-string">'million'</span>: <span class="hljs-string">'000000'</span>,
    <span class="hljs-string">'Million'</span>: <span class="hljs-string">'000000'</span>,
    <span class="hljs-string">','</span>: <span class="hljs-string">''</span>
}
</code></pre>
<pre><code class="lang-python">  <span class="hljs-comment">#If dollar matches isn't blank, regex matched on a dollar comment</span>
        <span class="hljs-keyword">if</span> dollar_matches:
            <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> replace_dict.items():
                dollar_matches = [w.replace(key, value) <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> dollar_matches]

            <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> dollar_matches:
                comment_list.append(item)
</code></pre>
<ol>
<li>The second biggest normalization was to isolate ranges and 'per' comments such as $50k/year. To do that, I used another regular expression to extract the maximum numerical value in a comment. This does reduce the accuracy if a comment happens to have two separate dollar references but I felt the reduction in noise was better than a few missed numbers.</li>
</ol>
<pre><code class="lang-python">    normalized_dollars_per_submission = []
    <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> comment_list:
        list_to_int = list(map(int, re.findall(<span class="hljs-string">r"\d+"</span>, x)))
        normalized_dollars_per_submission.append(max(list_to_int))
</code></pre>
<p>As an example, '$50K/year' will be normalized to '50000/year' and then extracted as just '50000' through this process.</p>
<h3 id="heading-statistics-and-writing-to-csv">Statistics and Writing to CSV</h3>
<p>Lastly, gather a few baseline stats using the statistics library and write to a CSV. </p>
<pre><code class="lang-python">average_amt = statistics.mean(all_normalized_comments)
max_amt = max(all_normalized_comments)
median_amt = statistics.median(all_normalized_comments)

ziplist = [average_amt,max_amt,median_amt]

csv_columns_second = [
    <span class="hljs-string">'average'</span>, <span class="hljs-string">'max'</span>, <span class="hljs-string">'median'</span>
]
csv_file = <span class="hljs-string">"fire_comments_aggregated.csv"</span>
<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">with</span> open(csv_file, <span class="hljs-string">'w'</span>,newline=<span class="hljs-string">""</span>) <span class="hljs-keyword">as</span> csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(csv_columns_second)
        writer.writerow(ziplist)

<span class="hljs-keyword">except</span> IOError:
    print(<span class="hljs-string">"I/O error"</span>)
</code></pre>
<h3 id="heading-plotting-by-daily-post">Plotting by Daily Post</h3>
<p>This was an aside and not something I originally set out to do, mainly because this analysis doesn't provide many meaningful insights but it's interesting to look at nonetheless! Below is the median dollar amount referenced in all top level comments for the FIRE subreddit daily post, plotted by day for the past 250 days.</p>
<pre><code class="lang-python">df = pd.read_csv(<span class="hljs-string">'fire_comments.csv'</span>, parse_dates=[<span class="hljs-string">'date'</span>],
                 index_col=[<span class="hljs-string">'date'</span>]) 


fig, ax = plt.subplots(figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
plt.xlabel(<span class="hljs-string">'Date'</span>)
plt.ylabel(<span class="hljs-string">'Median'</span>)
plt.suptitle(<span class="hljs-string">'Median Dollar Amount Commented on FIRE Subreddit Daily Posts'</span>)

plt.scatter(df.index.values, df.mediann)


ax.xaxis.set_major_locator(mdates.WeekdayLocator(interval=<span class="hljs-number">3</span>))
ax.xaxis.set_major_formatter(DateFormatter(<span class="hljs-string">"%m/%d"</span>))

plt.show()
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642174760134/xOtI24qaw.png" alt="plot.png" /></p>
<h3 id="heading-aggregate-conclusion">Aggregate Conclusion</h3>
<p>The following statistics are from the top level normalized comments of the FIRE subreddit daily posts for the period: Aug 11th 2020 - June 16th 2021</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Statistic</td><td>Value</td></tr>
</thead>
<tbody>
<tr>
<td>Average</td><td>$161,132,479,080</td></tr>
<tr>
<td>Median</td><td>$5,000</td></tr>
<tr>
<td>Max</td><td>$800,000,000,000,000</td></tr>
</tbody>
</table>
</div><hr />
]]></content:encoded></item><item><title><![CDATA[Automate a Cryptocurrency Portfolio using Python]]></title><description><![CDATA[I got caught up in the cryptocurrency craze back in 2017; the new hype in 2021 has piqued my interest again. There are some great crypto applications out there to track your portfolio, however I wanted to create my own.
‍_
Using Python and AWS Lambda...]]></description><link>https://datatribute.com/automate-a-cryptocurrency-portfolio-using-python</link><guid isPermaLink="true">https://datatribute.com/automate-a-cryptocurrency-portfolio-using-python</guid><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[Python]]></category><category><![CDATA[#data visualisation]]></category><dc:creator><![CDATA[Adam]]></dc:creator><pubDate>Fri, 14 Jan 2022 00:59:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642121345476/dUBYrIlobH.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I got caught up in the cryptocurrency craze back in 2017; the new hype in 2021 has piqued my interest again. There are some great crypto applications out there to track your portfolio, however I wanted to create my own.</p>
<p>‍<strong><strong>_</strong></strong></p>
<p>Using Python and AWS Lambda I'm refreshing a Google Data Studio dashboard via Google Sheets with only a few lines of code.</p>
<p>See my other <a target="_blank" href="https://datatribute.com/automate-the-luxury-stuff-with-python">post</a> on scraping my local bike shops eCommerce page for an in-depth look at deployment via AWS Lambda, this article will mainly focus on Google authentication, sheets, data studio and the cryptocompare package.</p>
<h3 id="heading-create-a-google-cloud-project">Create a Google Cloud Project</h3>
<ul>
<li>If you don't already have a Google Cloud account, login with your Gmail to get started</li>
<li>Create a New Project or use the default project created for you</li>
<li>Navigate to API's &amp; Services</li>
<li>Enable the Google Drive's API service associated with the Gmail you are logged into</li>
<li>Search for Google Drive and Enable Service</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642121437168/BpUdPzvts.png" alt="60b003fb42c21d1d48505000_API_gcp.png" /></p>
<h3 id="heading-create-a-service-account">Create a Service Account</h3>
<ul>
<li>APIs &amp; Services -&gt; Credentials -&gt; Create Credentials -&gt; Service Account</li>
<li>Name fields however you like, create and SAVE the JSON file</li>
<li>This JSON file is your authentication file and should be placed in the root folder of your Python file</li>
<li>Take note of the iam.gserviceaccount.com email - it should also be present in your JSON file
‍</li>
</ul>
<h3 id="heading-create-a-google-sheet">Create a Google Sheet</h3>
<ul>
<li>Create a blank Google Sheet</li>
<li>Share the sheet with the service account gmail created in the previous step
‍</li>
</ul>
<h3 id="heading-query-crypto-prices-cryptocompare">Query Crypto Prices - cryptocompare</h3>
<ul>
<li>pip install cryptocompare</li>
<li>Plug in a list of coins into a list</li>
<li><em>Optional</em>: I have a few obscure coins and not all are available via USDT direct trade, as such I created a second list based on the currency to get historical data only. The get_avg function asks to specify an exchange - this is why you may need a currency specification based on your coins.</li>
<li>Loop over all coins and call get_price and get_avg</li>
<li>Append this data to empty lists created before the for loop</li>
</ul>
<pre><code class="lang-python">coin_list = [
        <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'XRP'</span>, <span class="hljs-string">'NANO'</span>, <span class="hljs-string">'OMG'</span>, <span class="hljs-string">'ARK'</span>, <span class="hljs-string">'SC'</span>, <span class="hljs-string">'XRP'</span>, <span class="hljs-string">'GNT'</span>, <span class="hljs-string">'NEO'</span>, <span class="hljs-string">'MIOTA'</span>,
        <span class="hljs-string">'REQ'</span>
    ]
    currency_list = [
        <span class="hljs-string">'USDT'</span>, <span class="hljs-string">'USDT'</span>, <span class="hljs-string">'USDT'</span>, <span class="hljs-string">'USDT'</span>, <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'BTC'</span>,
        <span class="hljs-string">'BTC'</span>, <span class="hljs-string">'BTC'</span>
    ]
    coin_price_dict = {}
    coin_price = []
    coin_change = []


    <span class="hljs-keyword">for</span> coin, curr <span class="hljs-keyword">in</span> zip(coin_list, currency_list):

        price = <span class="hljs-string">'${0:,.2f}'</span>.format(
            cryptocompare.get_price(coin, <span class="hljs-string">'USD'</span>)[coin][<span class="hljs-string">'USD'</span>])
        hist_price = <span class="hljs-string">'{0:,.2f}%'</span>.format(
            cryptocompare.get_avg(coin, currency=curr,
                                  exchange=<span class="hljs-string">'Binance'</span>)[<span class="hljs-string">'CHANGEPCT24HOUR'</span>])

        coin_price_dict[coin] = [price, hist_price]
        coin_price.append(price)
        coin_change.append(hist_price)
</code></pre>
<h3 id="heading-push-dataframe-to-google-sheets">Push Dataframe to Google Sheets</h3>
<ul>
<li>pip install pygsheets</li>
<li>Authenticate via secrets.json file (created from service account in previous step)
add the columns to the blank dataframe; these columns were built within the for loop in the previous step</li>
<li>Open sheet via pygsheets, select the first sheet and set the dataframe to begin on the first row and column</li>
</ul>
<pre><code class="lang-python">    <span class="hljs-comment">#authorization</span>
    gc = pygsheets.authorize(service_file=<span class="hljs-string">'secrets.json'</span>)

    <span class="hljs-comment"># Create empty dataframe</span>
    df = pd.DataFrame()

    <span class="hljs-comment"># Create a column</span>
    df[<span class="hljs-string">'Coin Name'</span>] = coin_list
    df[<span class="hljs-string">'Price'</span>] = coin_price
    df[<span class="hljs-string">'24hr Change'</span>] = coin_change

    <span class="hljs-comment">#open the google spreadsheet (where 'PY to Gsheet Test' is the name of my sheet)</span>
    sh = gc.open(<span class="hljs-string">'Crypto Dash'</span>)

    <span class="hljs-comment">#select the first sheet</span>
    wks = sh[<span class="hljs-number">0</span>]

    <span class="hljs-comment">#update the first sheet with df, starting at cell B2.</span>
    wks.set_dataframe(df, (<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))
</code></pre>
<h3 id="heading-google-data-studio-dashboard">Google Data Studio Dashboard</h3>
<ul>
<li>Copy this <a target="_blank" href="https://datastudio.google.com/u/0/reporting/443a684e-6603-479e-9be3-203f81ffe9d7/page/HerIC">Google Data Studio (GDS)</a> template or create your own!</li>
<li>Replace the data source with the Google Sheet you created</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642121630443/mQ6r1YQv7.png" alt="60b00fe9024e29a1b323b467_gds_ss.png" /></p>
<h3 id="heading-deploy-code-in-aws-lambda">Deploy Code in AWS Lambda</h3>
<ul>
<li>Package all dependencies and code into a zip file</li>
<li>Create a Lambda function and set your desired triggers
Important note about pandas and numpy for Lambda - there are a few commons errors that Lambda encounters when using pandas/numpy packages. If you have any errors related to these packages I suggest manually downloading pandas/numpy from pypi.org and plopping them into your zip file. You should download --your python version--manylinux--x86 -- 64, version of the package.</li>
<li>See my other blog <a target="_blank" href="https://datatribute.com/automate-the-luxury-stuff-with-python">post</a> for a detailed explanation of Lambda functions</li>
</ul>
]]></content:encoded></item></channel></rss>